Hi all,

I have probably reinvented the wheel here, however I thought it may be
useful if I share the modifications I have made to the multisync backup
plugin.

My intention was to synchronise calenders/contacts/tasks on several
computers running evolution, laptop <-> desktop etc...
This modified version of the backup plugin stores the entries in exactly
the same way as before, however it can detect when new files are added
to the directory and when files are removed/modified (based upon the
modification time stamp).

This enables several computers to sync to the same directory either by
NFS or using software such as Unison to keep a local copy of the backup
directory in sync with remote copies.

I have been testing this for a day or two, and it appears to be working
flawlessly for contacts and calendars. The handling of task data seems
to be a little broken, however I believe this not to be a fault of this
plugin.

Since the backup directory no-longer contains the backup_entries file,
synchronising multiple backups is much simplified, and it enables many
people to share calendars/contacts/tasks using the file-system, or
whatever directory level synchronisation method is preferred.

All comments welcome!

Regards,

-- 
Craig Shelley
EMail: [EMAIL PROTECTED]
Jabber: [EMAIL PROTECTED]
/* 
   MultiSync Backup Plugin - Backup your PIM data
   Copyright (C) 2002-2003 Bo Lincoln <[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;

   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: backup_plugin.c,v 1.9 2003/08/26 20:05:49 lincoln Exp $
 */
#include <stdlib.h>
#include <glib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <gmodule.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include <multisync.h>
#include "backup_plugin.h"
#include "gui.h"

#define BACKUPFILE "backup"

void backup_load_state(backup_connection *conn) {
  char *filename;
  FILE *f;
  char line[256];

  filename = g_strdup_printf("%s/%s%s", sync_get_datapath(conn->sync_pair), 
			     (conn->conntype==CONNECTION_TYPE_LOCAL?"local":"remote"), BACKUPFILE);
  if ((f = fopen(filename, "r"))) {
    while (fgets(line, 256, f)) {
      char prop[128], data[256];
      if (sscanf(line, "%128s = %256[^\n]", prop, data) == 2) {
	if (!strcmp(prop, "backupdir")) {
	  conn->backupdir = g_strdup(data);
	}
	if (!strcmp(prop, "rebackupall")) {
	  if (!strcmp(data, "yes"))
	    conn->rebackupall = TRUE;
	  else
	    conn->rebackupall = FALSE;
	}
	if (!strcmp(prop, "harddelete")) {
	  if (!strcmp(data, "yes"))
	    conn->harddelete = TRUE;
	  else
	    conn->harddelete = FALSE;
	}
      }
    }
    fclose(f);
  }
  g_free(filename);
}

void backup_save_state(backup_connection *conn) {
  FILE *f;
  char *filename;
  
  filename = g_strdup_printf("%s/%s%s", sync_get_datapath(conn->sync_pair),
			     (conn->conntype==CONNECTION_TYPE_LOCAL?"local":"remote"), BACKUPFILE);
  if ((f = fopen(filename, "w"))) {

    if (conn->backupdir)
      fprintf(f, "backupdir = %s\n", conn->backupdir);
    fprintf(f, "rebackupall = %s\n", conn->rebackupall?"yes":"no");
    fprintf(f, "harddelete = %s\n", conn->harddelete?"yes":"no");
    fclose(f);
  }
  g_free(filename);
}

//Return a line of data from a vCARD, vCALENDAR etc. Free the string 
// using g_free().
char* backup_get_entry_data(char *card, char *key) {
  char *pos = card;
  int l = strlen(key);

  while (pos) {
    if (!strncmp(pos, key, l) && (pos[l] == ':' || pos[l] == ';')) {
      char *start, *end;
      start = strstr(pos+l, ":");
      if (start) {
	start++;
	end = strstr(start, "\n");
	if (!end)
	  end = card+strlen(card);
	if (*(end-1) == '\r')
	  end--;
	return(g_strndup(start, end-start));
      }
    }
    pos = strstr(pos, "\n");
    if (pos)
      pos += 1;
  }
  return(NULL);
}

#define BACKUPENTRYFILE "backup_entries"

void backup_load_entries(backup_connection *conn) {
  char *filename;
  FILE *f;
  char line[512];


  /* If the backupdir hasn't been configured, skip this function */
  if(conn->backupdir == NULL) return;

  filename = g_strdup_printf ("%s/%s", sync_get_datapath(conn->sync_pair), 
			      BACKUPENTRYFILE);
  if ((f = fopen(filename, "r"))) {
    while (fgets(line, 512, f)) {
      char uid[256];
      int objtype, mod_time;
      if (sscanf(line, "%d %u %256s", &objtype, &mod_time, uid) >= 3) {
	backup_object *entry = g_malloc(sizeof(backup_object));
	g_assert(entry);
	entry->uid = g_strdup(uid);
	entry->object_type = objtype;
	entry->mod_time = mod_time;
	conn->entries = g_list_append(conn->entries, entry);
      }
    }
    fclose(f);
  }
  g_free(filename);
}

void backup_save_entries(backup_connection *conn) {
  char *filename;
  FILE *f;

  filename = g_strdup_printf ("%s/%s", sync_get_datapath(conn->sync_pair), 
			      BACKUPENTRYFILE);
  if ((f = fopen(filename, "w"))) {
    GList *l;
    for (l=conn->entries; l; l = l->next) {
      int changetype;
      int objtype;
      backup_object *entry;
      entry = l->data;
      objtype = entry->object_type;
      changetype = entry->type;
      fprintf(f, "%d %u %s\n", objtype, entry->mod_time, entry->uid);
    }
    fclose(f);
  }
  g_free(filename);
}

void backup_free_entries(backup_connection *conn) {
  while (conn->entries) {
    GList *entries;
    backup_object *entry;
    
    entries = g_list_first(conn->entries);
    if (entries->data) {
      entry = entries->data;
      if (entry->uid) 
	g_free(entry->uid);
      g_free(entry);
    }
    conn->entries = g_list_remove_link(conn->entries, entries);
  }
}

gboolean backup_do_connect(gpointer data) {
  backup_connection *conn = data;
  struct stat statbuf;

  if (!conn->backupdir) {
    backup_show_msg("Backup plugin: Please set the backup directory\nin the backup options first.");
    goto err;
  }
  
  if (stat(conn->backupdir, &statbuf) == -1) {
    if (mkdir(conn->backupdir, 0700))
      goto err;
  }
  backup_load_entries(conn);
  sync_set_requestdone(conn->sync_pair);
  return(FALSE);
  err:
  sync_set_requestfailed(conn->sync_pair);
  return(FALSE);
}  

backup_connection* sync_connect(sync_pair* handle, connection_type type,
				sync_object_type object_types) {
  backup_connection *conn;
  conn = g_malloc0(sizeof(backup_connection));
  conn->sync_pair = handle;
  conn->conntype = type;
  backup_load_state(conn);
  
  g_idle_add(backup_do_connect, conn);
  return(conn);
}

void backup_free_connection(backup_connection *conn) {
  if (conn) {
    backup_free_entries(conn);
    if (conn->backupdir)
      g_free(conn->backupdir);
    g_free(conn);
  }
}

void sync_disconnect(backup_connection *conn) {
  sync_pair *sync_pair = conn->sync_pair;
  backup_free_connection(conn);
  sync_set_requestdone(sync_pair);
}

typedef struct {
  backup_connection *conn;
  sync_object_type newdbs;
} backup_get_changes_arg;


gboolean backup_do_get_changes(gpointer data) {
  backup_get_changes_arg *arg = data;
  backup_connection *conn = arg->conn;
  int t;
  GList *changes = NULL;
  change_info *chinfo;
  sync_object_type newdbs = arg->newdbs;

  g_free(arg);
  if (newdbs) {
    gboolean ask = FALSE;
    for (t = 0; t < g_list_length(conn->entries); t++) {
      backup_object *entry = g_list_nth_data(conn->entries, t);
      if (newdbs & entry->object_type)
	ask = TRUE;
    }
    if (ask) {
      if (!backup_show_question("One or more of the other side databases\nseem to have been reset.\nWould you like to restore the data from backup?"))
	newdbs = 0;
    }
  }

  DIR *d = opendir(conn->backupdir);
  if (d) {
    struct dirent *direntry;
    while (direntry=readdir(d)) {
      char * fname = g_strdup_printf("%s/%s", conn->backupdir, direntry->d_name);
      struct stat statbuf;
      if (!stat(fname, &statbuf)) {
	if (S_ISREG(statbuf.st_mode)) {
	  
	  /*Try to find the entry for this file*/
	  backup_object *found_entry=NULL;
	  for (t = 0; t < g_list_length(conn->entries); t++) {
	    backup_object *entry = g_list_nth_data(conn->entries, t);
	    if (strcmp(direntry->d_name, entry->uid) == 0) {
	      found_entry = entry;
	      break;
	    }
	  }

	  /* If no entry was found for this file*/
	  if (!found_entry) {
	    /* New file found */
	    printf("BACKUP: New file %s\n", fname);
	    backup_object *entry = g_malloc(sizeof(backup_object));
	    g_assert(entry);
	    entry->uid = g_strdup(direntry->d_name);
	    entry->mod_time = (int) statbuf.st_mtime;
	    conn->entries = g_list_append(conn->entries, entry);

	    /* Load the changes */
	    changed_object *change = g_malloc0(sizeof(changed_object));
	    change->comp = g_malloc0(statbuf.st_size+1);
	    FILE *f;
	    if ((f = fopen(fname, "r"))) {
	      fread(change->comp, 1, statbuf.st_size, f);
	      fclose(f);
	    }
	    if (strncmp(change->comp, "BEGIN:VCALENDAR", 15) == 0 && strlen(change->comp) > 30)
	    {
	      int i;
	      entry->object_type = SYNC_OBJECT_TYPE_UNKNOWN;
	      for (i=15; i<20 && change->comp[i] != 'B'; i++);
	      if (strncmp(change->comp + i, "BEGIN:VEVENT", 12) == 0)
		entry->object_type = SYNC_OBJECT_TYPE_CALENDAR;
	      if (strncmp(change->comp + i, "BEGIN:VTODO", 11) == 0)
		entry->object_type = SYNC_OBJECT_TYPE_TODO;
	    }
	    if (strncmp(change->comp, "BEGIN:VCARD", 11) == 0)
	      entry->object_type = SYNC_OBJECT_TYPE_PHONEBOOK;

	    printf("BACKUP: type = %i\n", entry->object_type);

	    change->uid = g_strdup(entry->uid);
	    change->change_type = SYNC_OBJ_ADDED;
	    change->object_type = entry->object_type;
	    changes = g_list_append(changes, change);

	  } else {
	    /* Existing entry, check modification time*/
	    if (found_entry->mod_time != (int) statbuf.st_mtime) {
	      printf("BACKUP: File modified %s type = %i\n", fname, found_entry->object_type);
	      found_entry->mod_time = (int) statbuf.st_mtime;
	      /* Load the changes */
	      changed_object *change = g_malloc0(sizeof(changed_object));
	      FILE *f = NULL;
	      change->comp = g_malloc0(statbuf.st_size+1);
	      if ((f = fopen(fname, "r"))) {
		fread(change->comp, 1, statbuf.st_size, f);
		fclose(f);
	      }
	      change->uid = g_strdup(found_entry->uid);
	      change->change_type = SYNC_OBJ_MODIFIED;
	      change->object_type = found_entry->object_type;
	      changes = g_list_append(changes, change);
	    }
	  }

	}
      }
      g_free(fname);
    }
    closedir(d);
  }

  GList *entries = g_list_first(conn->entries);
  while(entries) {
    backup_object *entry = entries->data;
    entries = g_list_next(entries);
    if (entry) {
      char * fname = g_strdup_printf("%s/%s", conn->backupdir, entry->uid);
      struct stat statbuf;
      if (stat(fname, &statbuf)) {
	printf("BACKUP: File deleted %s type = %i\n", fname, entry->object_type);
	changed_object *change = g_malloc0(sizeof(changed_object));
	change->uid = g_strdup(entry->uid);
	change->change_type = SYNC_OBJ_HARDDELETED;
	change->object_type = entry->object_type;
	changes = g_list_append(changes, change);

	conn->entries = g_list_remove(conn->entries, entry);
	if (entry->uid) {
	  g_free(entry->uid);
	  g_free(entry);
	}
	g_free(fname);
      }
    }
  }
  
  printf("BACKUP: Found %i changes.\n", g_list_length(changes));
  
  chinfo = g_malloc0(sizeof(change_info));
  chinfo->changes = changes;
  chinfo->newdbs = 0;
  sync_set_requestdata(chinfo, conn->sync_pair);
  return(FALSE);
}

void get_changes(backup_connection* conn, sync_object_type newdbs) { 
  backup_get_changes_arg *arg;

  if (conn->rebackupall) {
    change_info *chinfo = g_malloc0(sizeof(change_info));
    chinfo->newdbs = SYNC_OBJECT_TYPE_ANY;
    sync_set_requestdata(chinfo, conn->sync_pair);
    backup_free_entries(conn);
    backup_save_state(conn);
    return;
  }
  arg = g_malloc0(sizeof(backup_get_changes_arg));
  arg->conn = conn;
  arg->newdbs = newdbs;
 // g_idle_add(backup_do_get_changes, arg);
 backup_do_get_changes(arg);
}


void sync_done(backup_connection *conn, gboolean success) {
  if (success) {
    int t;
    for (t = 0; t < g_list_length(conn->entries); t++) {
      backup_object *entry = g_list_nth_data(conn->entries, t);
      if (entry && (entry->type == BACKUP_TYPE_RESTORE ||
		    entry->type == BACKUP_TYPE_REBACKUP))
	entry->type = BACKUP_TYPE_SAVED;
    }
    if (conn->rebackupall) {
      conn->rebackupall = FALSE;
      backup_save_state(conn);
    }
    backup_save_entries(conn);
  }
  sync_set_requestdone(conn->sync_pair);
}
 

void backup_hard_delete(backup_connection *conn, backup_object *entry) {
  char *fname;
  if (entry) {
    conn->entries = g_list_remove(conn->entries, entry);
    fname = g_strdup_printf("%s/%s", conn->backupdir, entry->uid);
    unlink(fname);
    g_free(fname);
    if (entry->uid)
      g_free(entry->uid);
    g_free(entry);
  }
}

void backup_modify_or_delete(backup_connection *conn, 
			     char *comp, 
			     char *uid, sync_object_type objtype, 
			     char *uidret, int *uidretlen,
			     gboolean softdelete) {
  char *luid = NULL;
  backup_object *entry = NULL;
  int t;

  if (!uid && !comp) {
    sync_set_requestfailed(conn->sync_pair);
    return;
  }
  if (uid)
    luid = g_strdup(uid);
  if (!luid) {
    // Generate a UID
    int count = 0;
    while (!luid) {
      char *fname;
      struct stat statbuf;
      luid = g_strdup_printf("multisync%d-%d", (int) time(NULL), count);
      fname = g_strdup_printf("%s/%s", conn->backupdir, luid);
      if (!stat(fname, &statbuf)) {
	count++;
	g_free(luid);
	luid = NULL;
      }
      g_free(fname);
    }
  }
  for (t = 0; t < g_list_length(conn->entries); t++) {
    backup_object *e;
    e = g_list_nth_data(conn->entries, t);
    if (e->uid && !strcmp(e->uid, luid)) {
      entry = e;
    }
  }
  if (!entry && uid) {
    // Non-existing UID given
    sync_set_requestfailed(conn->sync_pair);
    return;
  }
  if (!entry) {
    // If no entry is found, add it
    entry = g_malloc0(sizeof(backup_object));
    entry->uid = g_strdup(luid);
    conn->entries = g_list_append(conn->entries, entry);
  }
  entry->object_type = objtype;
  
  if (comp)
    entry->type = BACKUP_TYPE_SAVED;
  else {
    entry->type = BACKUP_TYPE_DELETED;
  }
  if (conn->harddelete && !comp)
    backup_hard_delete(conn, entry);
  backup_save_entries(conn);

  if (comp) {
    // Save the data
    char *fname, *objtypestr;
    FILE *f;
    fname = g_strdup_printf("%s/%s", conn->backupdir, luid);
    //objtypestr = g_strdup_printf("%u\n", entry->object_type);
    if ((f = fopen(fname, "w"))) {
    //  fputs(objtypestr, f);
      fputs(comp, f);
      fclose(f);
    }
    struct stat statbuf;
    if (!stat(fname, &statbuf)) {
      entry->mod_time = (int) statbuf.st_mtime;
    }
    //g_free(objtypestr);
    g_free(fname);
  }
  if (!uid && uidret) {
    // Return our generated LUID
    strncpy(uidret, luid, *uidretlen);
    *uidretlen = strlen(luid);
  }
  g_free(luid);
  sync_set_requestdone(conn->sync_pair);
}

void syncobj_modify(backup_connection *conn, 
		    char *comp, 
		    char *uid, sync_object_type objtype, 
		    char *uidret, int *uidretlen) {
  backup_modify_or_delete(conn, comp, uid, objtype, uidret, uidretlen, FALSE);
}

void syncobj_delete(backup_connection *conn, char *uid, 
		    sync_object_type objtype, int softdelete) {
  backup_modify_or_delete(conn, NULL, uid, objtype, NULL, NULL, softdelete);
}

// Return TRUE if this client does not have to be polled 
// (i.e. can be constantly connected)
gboolean always_connected() {
  return(TRUE);
}

char* short_name() {
  return("backup-plugin");
}

char* long_name() {
  return("Backup");
}

// Return the types of objects that this client handle
sync_object_type object_types() {
  return(SYNC_OBJECT_TYPE_ANY);
}



void plugin_init(void) {
}

char* plugin_info(void) {
  return("This plugin keeps a backup of your data. You can manage the stored entries is the plugin options (above).");
}

int plugin_API_version(void) {
  return(3);
}
#ifndef BACKUP_PLUGIN_H
#define BACKUP_PLUGIN_H

#include <multisync.h>

typedef enum {
  BACKUP_TYPE_SAVED = 1,
  BACKUP_TYPE_DELETED = 2,
  BACKUP_TYPE_RESTORE = 3,
  BACKUP_TYPE_REBACKUP = 4
} backup_type;

typedef struct {
  char *uid;
  backup_type type;
  sync_object_type object_type;
  int mod_time;
} backup_object;

typedef struct {
  client_connection commondata;
  sync_pair *sync_pair;
  connection_type conntype;
  char *backupdir;
  GList *entries; // The backup entries we have
  gboolean rebackupall;
  gboolean harddelete;
} backup_connection;

void backup_load_state(backup_connection *conn);
void backup_save_state(backup_connection *conn);
void backup_load_entries(backup_connection *conn);
char* backup_get_entry_data(char *card, char *key);
void backup_save_entries(backup_connection *conn);
void backup_free_entries(backup_connection *conn);
void backup_hard_delete(backup_connection *conn, backup_object *entry);
void backup_modify_or_delete(backup_connection *conn, 
			     char *comp, 
			     char *uid, sync_object_type objtype, 
			     char *uidret, int *uidretlen,
			     gboolean softdelete);
void backup_free_connection(backup_connection *conn);
gboolean backup_do_connect(gpointer data);

backup_connection* sync_connect(sync_pair* handle, connection_type type,
				sync_object_type object_types);
void sync_disconnect(backup_connection *conn);
void sync_done(backup_connection *conn, gboolean success);
void syncobj_modify(backup_connection *conn, 
		    char *comp, 
		    char *uid, sync_object_type objtype, 
		    char *uidret, int *uidretlen);
void syncobj_delete(backup_connection *conn, char *uid, 
		    sync_object_type objtype, int softdelete);
gboolean backup_do_get_changes(gpointer data);
void get_changes(backup_connection* conn, sync_object_type newdbs);
gboolean always_connected(void);
char* short_name(void);
char* long_name(void);
sync_object_type object_types(void);
void plugin_init(void);
char* plugin_info(void);




#endif

Attachment: signature.asc
Description: This is a digitally signed message part

Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Multisync-devel mailing list
Multisync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/multisync-devel

Reply via email to