/* MultiSync Evolution Plugin - Synchronize Ximian Evolution data Copyright (C) 2002-2003 Bo Lincoln 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: evolution_sync.c,v 1.49 2004/02/09 18:53:28 lincoln Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "evolution_sync.h" #include #include "addr_sync.h" #include "gui.h" gboolean versionok = FALSE; extern gboolean multisync_debug; void evo_load_state(evolution_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"), EVOLUTIONFILE); 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, "calendarpath")) { conn->calendarpath = g_strdup(data); } if (!strcmp(prop, "todopath")) { conn->todopath = g_strdup(data); } if (!strcmp(prop, "addressbookpath")) { conn->addressbookpath = g_strdup(data); } } } fclose(f); } g_free(filename); } void evo_save_state(evolution_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"), EVOLUTIONFILE); if ((f = fopen(filename, "w"))) { if (conn->calendarpath) fprintf(f, "calendarpath = %s\n", conn->calendarpath); if (conn->todopath) fprintf(f, "todopath = %s\n", conn->todopath); if (conn->addressbookpath) fprintf(f, "addressbookpath = %s\n", conn->addressbookpath); fclose(f); } g_free(filename); } sync_object_type object_type_from_component(CalComponent *comp) { sync_object_type type = SYNC_OBJECT_TYPE_UNKNOWN; CalComponentVType objtype; if (comp) { objtype = cal_component_get_vtype(comp); switch(objtype) { case CAL_COMPONENT_EVENT: type = SYNC_OBJECT_TYPE_CALENDAR; break; case CAL_COMPONENT_TODO: type = SYNC_OBJECT_TYPE_TODO; break; default: type = SYNC_OBJECT_TYPE_UNKNOWN; } } return (type); } // Like g_list_append, but checks for duplicates GList *evo_append_change(GList *changelist, changed_object *change) { int t; for (t = 0; t < g_list_length(changelist); t++) { changed_object *c; c = g_list_nth_data(changelist, t); if (c && c->uid && change->uid && !strcmp(change->uid, c->uid)) { sync_free_changed_object(c); changelist = g_list_remove(changelist, c); t--; } } changelist = g_list_append(changelist, change); return(changelist); } GList *evo_cal_get_changes(GList *changes, evolution_connection *conn) { GList *cal_changes, *l; if (!conn->cal_client) return(changes); cal_changes = cal_client_get_changes(conn->cal_client, CALOBJ_TYPE_EVENT, conn->changedbname); for (l=cal_changes; l; l = l->next) { CalClientChange *clientchange; CalComponent *comp; icalcomponent *icalcomp; clientchange = l->data; comp = clientchange->comp; if (comp) { char *uid = NULL; changed_object* change; icalproperty *dtend; change = g_malloc0(sizeof(changed_object)); g_assert(change); cal_component_get_uid(comp, (const char**) &uid); if (uid) change->uid = g_strdup(uid); icalcomp = cal_component_get_icalcomponent (comp); if (icalcomp) { change->comp = g_strdup_printf("BEGIN:VCALENDAR\r\nVERSION:2.0\r\n%s" "END:VCALENDAR\r\n", cal_component_get_as_string(comp)); if (clientchange->type & CAL_CLIENT_CHANGE_DELETED) change->change_type = SYNC_OBJ_HARDDELETED; else if (clientchange->type & CAL_CLIENT_CHANGE_MODIFIED) change->change_type = SYNC_OBJ_MODIFIED; else change->change_type = SYNC_OBJ_ADDED; dtend = icalcomponent_get_first_property(icalcomp, ICAL_DTEND_PROPERTY); if (dtend) change->removepriority = g_strdup(icaltime_as_ical_string(icalproperty_get_dtend(dtend))); change->object_type = object_type_from_component(comp); changes = evo_append_change(changes, change); } } } cal_client_change_list_free (cal_changes); return(changes); } GList *evo_todo_get_changes(GList *changes, evolution_connection *conn) { GList *todo_changes, *l; if (!conn->todo_client) return(changes); todo_changes = cal_client_get_changes(conn->todo_client, CALOBJ_TYPE_TODO, conn->changedbname); for (l=todo_changes; l; l = l->next) { CalClientChange *clientchange; CalComponent *comp; icalcomponent *icalcomp; clientchange = l->data; comp = clientchange->comp; if (comp) { char *uid = NULL; changed_object* change; icalproperty *dtend; change = g_malloc0(sizeof(changed_object)); g_assert(change); cal_component_get_uid(comp, (const char**) &uid); if (uid) change->uid = g_strdup(uid); icalcomp = cal_component_get_icalcomponent (comp); if (icalcomp) { change->comp = g_strdup_printf("BEGIN:VCALENDAR\r\nVERSION:2.0\r\n%s" "END:VCALENDAR\r\n", cal_component_get_as_string(comp)); if (clientchange->type & CAL_CLIENT_CHANGE_DELETED) change->change_type = SYNC_OBJ_HARDDELETED; else if (clientchange->type & CAL_CLIENT_CHANGE_MODIFIED) change->change_type = SYNC_OBJ_MODIFIED; else change->change_type = SYNC_OBJ_ADDED; dtend = icalcomponent_get_first_property(icalcomp, ICAL_DTEND_PROPERTY); if (dtend) change->removepriority = g_strdup(icaltime_as_ical_string(icalproperty_get_dtend(dtend))); change->object_type = object_type_from_component(comp); changes = evo_append_change(changes, change); } } } cal_client_change_list_free (todo_changes); return(changes); } gboolean evo_get_changes(gpointer data) { evolution_connection *conn = data; GList *changes = NULL; if (conn->cal_client) { if (!conn->modifying && (conn->newdbs & SYNC_OBJECT_TYPE_CALENDAR)) changes = evo_cal_get_all(changes, conn); else changes = evo_cal_get_changes(changes, conn); } if (conn->todo_client) { if (!conn->modifying && (conn->newdbs & SYNC_OBJECT_TYPE_TODO)) changes = evo_todo_get_all(changes, conn); else changes = evo_todo_get_changes(changes, conn); } if (!conn->modifying && (conn->newdbs & SYNC_OBJECT_TYPE_PHONEBOOK)) evo_addr_get_all(changes, conn, evo_get_changes_done); else evo_addr_get_changes(changes, conn, evo_get_changes_done); return(FALSE); } // Callback from addressbook get-changes void evo_get_changes_done(gpointer data, evolution_connection *conn) { GList *changes = data; change_info *chinfo; if (conn->modifying) { int n = 0; while (n < g_list_length(changes)) { changed_object *change = g_list_nth_data(changes, n); if (evo_check_change(conn, change->uid, change->change_type)) { changes = g_list_remove(changes, change); sync_free_changed_object(change); } else n++; } if (g_list_length(changes) > 0) { dd(printf("We found %d unexpected changes\n", g_list_length(changes))); add_internal_changes(changes, conn); // Add to internal list sync_object_changed(conn->sync_pair); // New, unexpected change sync_free_changes(changes); } sync_set_requestdata(conn->modify_results, conn->sync_pair); conn->modifying = FALSE; conn->modify_results = NULL; conn->modify_objects = NULL; } else { add_internal_changes(changes, conn); // Add to internal list sync_free_changes(changes); changes = get_internal_changes(conn); // Get all changes chinfo = g_malloc0(sizeof(change_info)); chinfo->changes = changes; chinfo->newdbs = 0; sync_set_requestdata(chinfo, conn->sync_pair); } } // Return TRUE fo this change was expected (we did it) gboolean evo_check_change(evolution_connection *conn, char *id, int change_type) { GList *changes = conn->modify_objects, *results = conn->modify_results; gboolean same = FALSE; while (changes && results && !same) { changed_object *change = changes->data; syncobj_modify_result *result = results->data; if ((id && result->returnuid && !strcmp(result->returnuid, id)) || (id && change->uid && !strcmp(change->uid, id))) { if ((change->change_type == SYNC_OBJ_MODIFIED || change->change_type == SYNC_OBJ_ADDED) && (change_type == SYNC_OBJ_HARDDELETED || change_type == SYNC_OBJ_SOFTDELETED)) same = FALSE; else same = TRUE; } changes = changes->next; results = results->next; } return(same); } GList *evo_cal_get_all (GList *changes, evolution_connection *conn) { GList *cal_changes, *known_changes, *l; if (!conn->cal_client) return(changes); cal_changes = cal_client_get_uids(conn->cal_client, CALOBJ_TYPE_EVENT); for (l=cal_changes; l; l = l->next) { CalComponent *comp; icalcomponent *icalcomp; char *uid; if (cal_client_get_object(conn->cal_client, l->data, &comp) == CAL_CLIENT_GET_SUCCESS) { changed_object* change; icalproperty *dtend; change = g_malloc0(sizeof(changed_object)); g_assert(change); cal_component_get_uid(comp, (const char**) &uid); if (uid) change->uid = g_strdup(uid); icalcomp = cal_component_get_icalcomponent (comp); change->comp = g_strdup_printf("BEGIN:VCALENDAR\r\nVERSION:2.0\r\n%s" "END:VCALENDAR\r\n", cal_component_get_as_string(comp)); change->change_type = SYNC_OBJ_MODIFIED; change->object_type = object_type_from_component(comp); dtend = icalcomponent_get_first_property(icalcomp, ICAL_DTEND_PROPERTY); if (dtend) change->removepriority = g_strdup(icaltime_as_ical_string(icalproperty_get_dtend(dtend))); changes = evo_append_change(changes, change); } } known_changes = cal_client_get_changes(conn->cal_client, CALOBJ_TYPE_EVENT, conn->changedbname); cal_obj_uid_list_free(known_changes); cal_obj_uid_list_free(cal_changes); return(changes); } GList *evo_todo_get_all (GList *changes, evolution_connection *conn) { GList *todo_changes, *known_changes, *l; if (!conn->todo_client) return(changes); todo_changes = cal_client_get_uids(conn->todo_client, CALOBJ_TYPE_TODO); for (l=todo_changes; l; l = l->next) { CalComponent *comp; icalcomponent *icalcomp; char *uid; if (cal_client_get_object(conn->todo_client, l->data, &comp) == CAL_CLIENT_GET_SUCCESS) { changed_object* change; change = g_malloc0(sizeof(changed_object)); g_assert(change); cal_component_get_uid(comp, (const char**) &uid); if (uid) change->uid = g_strdup(uid); icalcomp = cal_component_get_icalcomponent (comp); change->comp = g_strdup_printf("BEGIN:VCALENDAR\r\nVERSION:2.0\r\n%s" "END:VCALENDAR\r\n", cal_component_get_as_string(comp)); change->change_type = SYNC_OBJ_MODIFIED; change->object_type = object_type_from_component(comp); changes = evo_append_change(changes, change); } } known_changes = cal_client_get_changes(conn->todo_client, CALOBJ_TYPE_TODO, conn->changedbname); cal_obj_uid_list_free(known_changes); cal_obj_uid_list_free(todo_changes); return(changes); } // Add a list of changes to the internal list of changes (for // interrupted synchronizations etc). This is only a workaround // for evolution's stupid change-db handling. void add_internal_changes(GList *changes, evolution_connection *conn) { GList *l; for (l=changes; l; l = l->next) { changed_object *change = l->data; changed_object *tmp = sync_copy_changed_object(change); // Copy the changed object, and expand the struct internal_changed_object *obj = g_malloc0(sizeof(internal_changed_object)); memcpy(obj, tmp, sizeof(changed_object)); obj->reported = FALSE; g_free(tmp); conn->internal_changes = evo_append_change(conn->internal_changes, (changed_object*) obj); } save_internal_changes(conn); } // Returns a list of "change_object"s for get_changes GList *get_internal_changes(evolution_connection *conn) { GList *l; GList *sync_changes = NULL; for (l=conn->internal_changes; l; l = l->next) { changed_object *change; internal_changed_object *intchg; change = sync_copy_changed_object(l->data); sync_changes = evo_append_change(sync_changes, change); intchg = l->data; intchg->reported = TRUE; } return(sync_changes); } void remove_internal_changes(evolution_connection *conn) { int n = 0; GList *sync_changes = NULL; while (n < g_list_length(conn->internal_changes)) { internal_changed_object *intchg = g_list_nth_data(conn->internal_changes, n); if (intchg && intchg->reported) { conn->internal_changes = g_list_remove(conn->internal_changes, intchg); sync_free_changed_object((changed_object*) intchg); } else n++; } save_internal_changes(conn); } void load_internal_changes(evolution_connection *conn) { char *filename; FILE *f; char *line = NULL; line = g_malloc(65536); filename = g_strdup_printf ("%s/%s%s", sync_get_datapath(conn->sync_pair), (conn->conntype==CONNECTION_TYPE_LOCAL?"local": "remote"), INTERNAL_CHANGE_FILE); if ((f = fopen(filename, "r"))) { while (fgets(line, 65536, f)) { char objtype, changetype; if (sscanf(line, "2 %c %c", &objtype, &changetype) >= 2) { char *pos = line+6; internal_changed_object *change = g_malloc0(sizeof(internal_changed_object)); change->obj.object_type = (objtype=='C'?SYNC_OBJECT_TYPE_CALENDAR: (objtype=='T'?SYNC_OBJECT_TYPE_TODO: SYNC_OBJECT_TYPE_PHONEBOOK)); change->obj.change_type = (changetype=='D'?SYNC_OBJ_SOFTDELETED: (changetype=='H'?SYNC_OBJ_HARDDELETED: (changetype=='A'?SYNC_OBJ_ADDED: SYNC_OBJ_MODIFIED))); change->obj.comp = evo_decode_line_to_string(pos); pos = strstr(pos, " "); if (pos) pos++; change->obj.uid = evo_decode_line_to_string(pos); pos = strstr(pos, " "); if (pos) pos++; change->obj.removepriority = evo_decode_line_to_string(pos); change->reported = FALSE; conn->internal_changes = evo_append_change(conn->internal_changes, (changed_object*) change); } } fclose(f); } g_free(filename); g_free(line); } // Encode string to one line without spaces. // Returned string is gmalloced char *evo_encode_string_to_line(char* instr) { GString *str; int len,t; char *ret = NULL; if (!instr) return(g_strdup("")); str = g_string_new(""); len = strlen(instr); for (t = 0; t < len; t++) { switch(instr[t]) { case '\\': g_string_append(str, "\\\\"); break; case '\n': g_string_append(str, "\\n"); break; case '\r': g_string_append(str, "\\r"); break; case ' ': g_string_append(str, "\\_"); break; case '\t': g_string_append(str, "\\t"); break; default: { char tmp[2] = " "; tmp[0] = instr[t]; g_string_append(str, tmp); } } } ret = str->str; g_string_free(str, FALSE); return(ret); } char* evo_decode_line_to_string(char *line) { GString *str; int len,t; char *ret = NULL; int mode = 0; if (!line) return(NULL); str = g_string_new(""); len = strlen(line); for (t = 0; t < len && line[t] != ' '; t++) { if (mode) { switch(line[t]) { case '\\': g_string_append(str, "\\"); break; case 'n': g_string_append(str, "\n"); break; case 'r': g_string_append(str, "\r"); break; case '_': g_string_append(str, " "); break; case 't': g_string_append(str, "\t"); break; } mode = 0; } else { if (line[t] == '\\') mode = 1; else { char tmp[2] = " "; tmp[0] = line[t]; g_string_append(str, tmp); } } } ret = str->str; g_string_free(str, FALSE); if (strlen(ret) == 0) { g_free(ret); ret = NULL; } return(ret); } void save_internal_changes(evolution_connection *conn) { char *filename; FILE *f; filename = g_strdup_printf ("%s/%s%s", sync_get_datapath(conn->sync_pair), (conn->conntype==CONNECTION_TYPE_LOCAL?"local": "remote"), INTERNAL_CHANGE_FILE); if ((f = fopen(filename, "w"))) { GList *l; for (l=conn->internal_changes; l; l = l->next) { char changetype, objtype; changed_object *change; char *comp, *uid, *removepriority; change = l->data; objtype = (change->object_type==SYNC_OBJECT_TYPE_CALENDAR?'C': (change->object_type==SYNC_OBJECT_TYPE_TODO?'T':'P')); changetype = (change->change_type==SYNC_OBJ_SOFTDELETED?'S': (change->change_type==SYNC_OBJ_HARDDELETED?'D': (change->change_type==SYNC_OBJ_ADDED?'A':'M'))); comp = evo_encode_string_to_line(change->comp); uid = evo_encode_string_to_line(change->uid); removepriority = evo_encode_string_to_line(change->removepriority); // 2 for version 2. fprintf(f, "2 %c %c %s %s %s\n", objtype, changetype, comp, uid, removepriority); g_free(comp); g_free(uid); g_free(removepriority); } fclose(f); } g_free(filename); } /* Callback used when a calendar is opened */ static void cal_opened_cb (CalClient *client, CalClientOpenStatus status, gpointer data) { evolution_connection *conn = data; if (status == CAL_CLIENT_OPEN_SUCCESS) { if (conn->callback) (conn->callback)(NULL, conn); // We have loaded the DB } else { sync_set_requestfailed(conn->sync_pair); g_object_unref (G_OBJECT (client)); } } /* Callback used when an object is updated */ static void obj_updated_cb (CalClient *client, const char *uid, gpointer data) { evolution_connection *conn = data; if (!conn->modifying) sync_object_changed(conn->sync_pair); } /* Callback used when an object is removed */ static void obj_removed_cb (CalClient *client, const char *uid, gpointer data) { evolution_connection *conn = data; if (!conn->modifying) sync_object_changed(conn->sync_pair); } /* Creates a calendar client and tries to load the specified URI into it */ CalClient* create_client (evolution_connection *conn, const char *uri, gboolean only_if_exists) { gboolean result; CalClient *client = NULL; client = cal_client_new (); if (!client) { evo_display_error("Evolution plugin: Could not connect to Evolution!"); return(NULL); } g_signal_connect (client, "obj_removed", G_CALLBACK (obj_removed_cb), conn); g_signal_connect (client, "cal_opened", G_CALLBACK (cal_opened_cb), conn); g_signal_connect (client, "obj_updated", G_CALLBACK (obj_updated_cb), conn); dd(printf ("Calendar loading `%s'...\n", uri)); result = cal_client_open_calendar (client, uri, only_if_exists); if (!result) { char *err = g_strdup_printf("Evolution plugin: Could not open \"%s\"!", uri); evo_display_error(err); g_free(err); return(NULL); } return(client); } void get_changes(evolution_connection* conn, sync_object_type newdbs) { conn->newdbs = newdbs; g_idle_add(evo_get_changes, conn); } gboolean cal_connect(gpointer data) { evolution_connection *conn = data; char *dir; conn->callback = evo_sync_loaddbs_cb; // To be called when loaded if (conn->commondata.object_types & SYNC_OBJECT_TYPE_CALENDAR) { conn->nodbs++; if (conn->calendarpath) { conn->cal_client = create_client (conn, conn->calendarpath, FALSE); } else { char *dir = g_strdup_printf ("%s/evolution/local/Calendar/calendar.ics", g_get_home_dir()); conn->cal_client = create_client (conn, dir, TRUE); g_free (dir); } } if (conn->commondata.object_types & SYNC_OBJECT_TYPE_TODO) { conn->nodbs++; if (conn->todopath) { conn->todo_client = create_client (conn, conn->todopath, FALSE); } else { dir = g_strdup_printf ("%s/evolution/local/Tasks/tasks.ics", g_get_home_dir()); conn->todo_client = create_client (conn, dir, TRUE); g_free (dir); } } if (conn->commondata.object_types & SYNC_OBJECT_TYPE_PHONEBOOK) { evo_addr_connect(conn); } if (conn->nodbs == 0) sync_set_requestdone(conn->sync_pair); return(FALSE); } void evo_sync_loaddbs_cb(gpointer data, evolution_connection *conn) { conn->nodbsloaded++; if (conn->nodbsloaded >= conn->nodbs) sync_set_requestdone(conn->sync_pair); // All DBs are loaded! } evolution_connection* sync_connect(sync_pair* handle, connection_type type, sync_object_type object_types) { evolution_connection *conn; int i; char *slash; char *datapath; if (!versionok) { sync_set_requestfailed(handle); return(NULL); } conn = g_malloc0(sizeof(evolution_connection)); g_assert(conn); conn->sync_pair = handle; conn->conntype = type; conn->commondata.object_types = object_types; evo_load_state(conn); datapath = sync_get_datapath(handle); i = strlen(datapath)-1; if (datapath[i] == '/') i--; while(i > 0 && datapath[i] != '/') i--; i++; conn->changedbname = g_strdup_printf("%s%s", (type==CONNECTION_TYPE_LOCAL?"local": "remote"), datapath+i); if ((slash = strstr(conn->changedbname, "/"))) slash[0] = 0; load_internal_changes(conn); g_idle_add(cal_connect, conn); return(conn); } gboolean evo_do_disconnect(gpointer data) { evolution_connection *conn = data; sync_free_changes(conn->internal_changes); if (conn->cal_client) g_object_unref (G_OBJECT (conn->cal_client)); conn->cal_client = NULL; if (conn->todo_client) g_object_unref (G_OBJECT (conn->todo_client)); conn->todo_client = NULL; evo_addr_disconnect(conn); sync_set_requestdone(conn->sync_pair); g_free(conn); return(FALSE); } void sync_disconnect(evolution_connection *conn) { g_idle_add(evo_do_disconnect, conn); } // Free the returned string using g_free() char *evo_replace(char *str, char *from, char* to) { GString *outstr; char *ret=NULL; char *pos = str, *last = str; outstr = g_string_new(""); while ((pos = strstr(pos, from))) { char *tmp = g_strndup(last, pos-last); g_string_append(outstr, tmp); g_free(tmp); g_string_append(outstr, to); pos+=strlen(from); last = pos; } g_string_append(outstr, last); ret = outstr->str; g_string_free(outstr, FALSE); return(ret); } gboolean evo_cal_modify_one(evolution_connection *conn, changed_object *obj, char **uidret) { // UID exists, this is modify CalComponent *calcomp = NULL; CalClientResult res = 0; int modified = 0; if (obj->comp) { icalcomponent *icalcomp; char *tmp = evo_replace(obj->comp, "\r\n", "\n"); char *start, *end; start = strstr(tmp, "BEGIN:VEVENT"); end = strstr(tmp, "END:VEVENT"); if (end) { end+=strlen("END:VEVENT"+1); end[0] = 0; } if (!start || !end) { start = strstr(tmp, "BEGIN:VTODO"); end = strstr(tmp, "END:VTODO"); if (end) { end+=strlen("END:VTODO"+1); end[0] = 0; } } if (!start) start = tmp; icalcomp = icalcomponent_new_from_string(start); g_free(tmp); calcomp = cal_component_new (); g_assert(calcomp); cal_component_set_icalcomponent (calcomp,icalcomp); if (obj->uid) { cal_component_set_uid (calcomp, obj->uid); } else { char* uid = cal_component_gen_uid(); cal_component_set_uid (calcomp, uid); if (uidret) *uidret = g_strdup(uid); } if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) res = cal_client_update_object (conn->cal_client, calcomp); if (obj->object_type == SYNC_OBJECT_TYPE_TODO) res = cal_client_update_object (conn->todo_client, calcomp); if (res != CAL_CLIENT_RESULT_SUCCESS && obj->uid) { // Modify failed, try adding char* uid = cal_component_gen_uid(); cal_component_set_uid (calcomp, uid); if (uidret) *uidret = g_strdup(uid); if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) res = cal_client_update_object (conn->cal_client, calcomp); if (obj->object_type == SYNC_OBJECT_TYPE_TODO) res = cal_client_update_object (conn->todo_client, calcomp); } modified = (res == CAL_CLIENT_RESULT_SUCCESS); icalcomponent_free(icalcomp); } else { if (obj->uid) { if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) { if (cal_client_remove_object(conn->cal_client, obj->uid) == CAL_CLIENT_RESULT_SUCCESS) modified = 1; } if (obj->object_type == SYNC_OBJECT_TYPE_TODO) { if (cal_client_remove_object(conn->todo_client, obj->uid) == CAL_CLIENT_RESULT_SUCCESS) modified = 1; } } } return(modified); } gboolean evo_cal_modify(gpointer data) { evolution_connection *conn = data; GList *objects = conn->modify_objects, *results = conn->modify_results; while (objects && results) { changed_object *object = objects->data; syncobj_modify_result *result = results->data; if (object->object_type == SYNC_OBJECT_TYPE_CALENDAR || object->object_type == SYNC_OBJECT_TYPE_TODO) { if (evo_cal_modify_one(conn, object, &(result->returnuid))) result->result = SYNC_MSG_REQDONE; } objects = objects->next; results = results->next; } if (conn->callback) (conn->callback)(NULL, conn); // We have done the modifications return(FALSE); } void evo_addr_modify_done_cb(gpointer data, evolution_connection *conn) { g_idle_add(evo_get_changes, conn); // Remove known changes } void evo_cal_modify_done_cb(gpointer data, evolution_connection *conn) { conn->callback = evo_addr_modify_done_cb; // Continue and modify addr g_idle_add(evo_addr_modify, conn); } void syncobj_modify_list(evolution_connection *conn, GList *changes) { GList *node = changes; conn->modify_results = NULL; while (node) { // Create an empty response list changed_object *obj = node->data; syncobj_modify_result *result = g_malloc0(sizeof(syncobj_modify_result)); result->result = SYNC_MSG_REQFAILED; conn->modify_results = g_list_append(conn->modify_results, result); node = node->next; } conn->modify_objects = changes; conn->callback = evo_cal_modify_done_cb; conn->modifying = TRUE; g_idle_add(evo_cal_modify, conn); } typedef struct { evolution_connection *conn; changed_object *obj; time_t origstart; GList *recurs; } cal_get_recurring_args; // Check if the recur instance is of our UID, then add to the // recur list. gboolean cal_recur_instance (CalComponent *comp, time_t instance_start, time_t instance_end, gpointer data) { cal_get_recurring_args *arg = data; struct icaltimetype dtstart; struct icaltimetype dtend; const char *summary; char *newsummary; char *uid; icalcomponent* icalcomp; changed_object *recurobj; icalcomponent* changedcomp; cal_component_get_uid(comp, (const char**) &uid); if (!strcmp(uid, arg->obj->uid) && g_list_length(arg->recurs) < 50) { if (arg->origstart != instance_start) { changed_object *recurobj; icalcomp = cal_component_get_icalcomponent (comp); changedcomp = icalcomponent_new_clone(icalcomp); recurobj = g_malloc0(sizeof(changed_object)); g_assert(recurobj); dtstart = icaltime_from_timet(instance_start, 0); dtend = icaltime_from_timet(instance_end, 0); icalcomponent_set_dtstart(changedcomp, dtstart); icalcomponent_set_dtend(changedcomp, dtend); recurobj->uid = g_strdup(uid); recurobj->change_type = SYNC_OBJ_RECUR; recurobj->object_type = object_type_from_component(comp); recurobj->removepriority = g_strdup(icaltime_as_ical_string(dtend)); summary = icalcomponent_get_summary(changedcomp); newsummary = g_strdup_printf("%s [Recur]", summary); icalcomponent_set_summary(changedcomp, newsummary); g_free(newsummary); recurobj->comp = g_strdup_printf("BEGIN:VCALENDAR\r\nVERSION:2.0\r\n%s" "END:VCALENDAR\r\n", icalcomponent_as_ical_string(changedcomp)); arg->recurs = g_list_append(arg->recurs, recurobj); icalcomponent_free(changedcomp); } } return(TRUE); } gboolean do_cal_get_recurring(gpointer data) { cal_get_recurring_args *arg = data; icalcomponent *comp; time_t recurstart; time_t recurend; comp = icalcomponent_new_from_string(arg->obj->comp); recurstart = icaltime_as_timet(icalcomponent_get_dtstart(comp)); recurend = recurstart + 3600*24*365*10; // Ten years ahead arg->origstart=recurstart; if (arg->obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) { cal_client_generate_instances (arg->conn->cal_client, CALOBJ_TYPE_EVENT|CALOBJ_TYPE_TODO, recurstart, recurend, cal_recur_instance, data); } sync_set_requestdata(arg->recurs,arg->conn->sync_pair); icalcomponent_free(comp); g_free(arg); return(FALSE); } // Return the recurances object in obj from dtstart to dtstart+one year void syncobj_get_recurring(evolution_connection *conn, changed_object *obj) { cal_get_recurring_args *arg; if (obj->comp && (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR || obj->object_type == SYNC_OBJECT_TYPE_TODO)) { char *rrule = sync_get_key_data(obj->comp, "RRULE"); if (rrule) { g_free(rrule); arg = g_malloc0(sizeof(cal_get_recurring_args)); g_assert(arg); arg->conn = conn; arg->obj = obj; g_idle_add(do_cal_get_recurring, arg); } else { sync_set_requestdata(NULL,conn->sync_pair); } } else sync_set_requestfailed(conn->sync_pair); return; } // Called by the syncengine after synchronization // (if success, we can discard our internal change list) void sync_done(evolution_connection *conn, gboolean success) { if (success) { // No need to do it asyncronously remove_internal_changes(conn); } sync_set_requestdone(conn->sync_pair); } // 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("evolution-sync"); } char* long_name() { return("Ximian Evolution"); } // Return the types of objects that this client handle sync_object_type object_types() { return(SYNC_OBJECT_TYPE_CALENDAR | SYNC_OBJECT_TYPE_TODO | SYNC_OBJECT_TYPE_PHONEBOOK); } void plugin_init(void) { int fd[2]; char version[256] = ""; g_type_init(); pipe(fd); if (!fork()) { dup2(fd[1],1); execlp("evolution", "evolution", "--version", NULL); close(fd[1]); exit(0); } if (read(fd[0],version,256) > 0) { int majver, minver = 0, micver = 0; if (sscanf(version,"Gnome evolution %d.%d.%d",&majver, &minver, &micver) >= 2) { dd(printf("Detected evolution %d.%d.%d.\n", majver, minver, micver)); if (majver > 1 || (majver==1 && minver >= 4)) versionok = TRUE; else evo_async_display_error("Evolution plugin: This plugin requires Evolution 1.4 or greater."); } } } char* plugin_info(void) { return("Plugin for Ximian Evolution."); } int plugin_API_version(void) { return(3); }