/* MultiSync - A PIM data synchronization program 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; In addition, as a special exception, Bo Lincoln gives permission to link the code of this program with the OpenSSL library (or with modified versions of OpenSSL that use the same license as OpenSSL), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. 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: syncengine.c,v 1.81 2004/04/12 02:51:37 irix Exp $ */ #include #include #include #include #include #include #include "syncengine.h" #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "sync_vtype.h" #include "interface.h" #include "support.h" #include "gui.h" #include "tray.h" #define IDFILE "ids" gpointer (*plugin_function)(); #define CALL_PLUGIN_ASYNC(mod, name, args) ((plugin_function=dlsym(mod->plugin,name))?(*plugin_function)args:sync_set_requestfailed(thissync)) #define CALL_PLUGIN(mod, name, args) ((plugin_function=dlsym(mod->plugin,name))?(*plugin_function)args:NULL) #define LOG_ERROR(logstring) { char *txt = g_strdup_printf("%s: %s.", logstring, thissync->errorstr?thissync->errorstr:sync_error_string(ret)); async_add_pairlist_log(thissync, txt, SYNC_LOG_ERROR); g_free(txt); } GList *pluginlist = NULL; GList *syncpairlist = NULL; char *datadir; gboolean multisync_debug = FALSE; #define dd(x) (multisync_debug?(x):0) GList* sync_load_ids(sync_pair *thissync) { char *filename; FILE *f; GList* list = NULL; char line[256]; filename = g_strdup_printf ("%s/%s", thissync->datapath, IDFILE); if ((f = fopen(filename, "r"))) { while (fgets(line, 256, f)) { char uid[128], luid[128], enddate[32], objtype; if (sscanf(line, "%c %32s %128s = %128s", &objtype, enddate, uid, luid) >= 3) { idpair *idp = g_malloc(sizeof(idpair)); g_assert(idp); idp->uid = g_malloc(strlen(uid)+1); g_assert(idp->uid); memcpy(idp->uid, uid, strlen(uid)+1); idp->luid = g_malloc(strlen(luid)+1); g_assert(idp->luid); memcpy(idp->luid, luid, strlen(luid)+1); if (strcmp(enddate, "X") != 0) idp->enddate = g_strdup(enddate); else idp->enddate = NULL; idp->object_type = (objtype=='C'?SYNC_OBJECT_TYPE_CALENDAR: (objtype=='T'?SYNC_OBJECT_TYPE_TODO: SYNC_OBJECT_TYPE_PHONEBOOK)); list = g_list_append(list ,idp); } } fclose(f); } g_free(filename); return(list); } void sync_save_ids(GList* list, sync_pair *thissync) { guint len = g_list_length(list); guint t; char *filename; FILE *f; filename = g_strdup_printf ("%s/%s", thissync->datapath, IDFILE); if ((f = fopen(filename, "w"))) { for (t = 0; t < len; t++) { char chartype; idpair *idp; idp = g_list_nth_data(list, t); chartype = (idp->object_type==SYNC_OBJECT_TYPE_CALENDAR?'C': (idp->object_type==SYNC_OBJECT_TYPE_TODO?'T':'P')); if (idp && idp->uid && idp->luid) fprintf(f, "%c %s %s = %s\n", chartype, idp->enddate?idp->enddate:"X", idp->uid, idp->luid); } fclose(f); } g_free(filename); } char* sync_get_luid(char *uid, sync_object_type objtype, sync_pair *handle) { GList *list = handle->iddb; guint len = g_list_length(list); guint t; for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp && idp->uid && idp->luid && objtype == idp->object_type && !strcmp(uid,idp->uid)) return(idp->luid); } return(0); } GList *sync_get_recur_luids(char *uid, sync_object_type objtype, sync_pair *handle) { GList *list = handle->iddb; GList *luidlist = NULL; guint len = g_list_length(list); guint t; char *recuruid; recuruid = g_strdup_printf("SYNCRECUR-%s", uid); for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp && idp->uid && idp->luid && objtype == idp->object_type && !strcmp(recuruid,idp->uid)) { luidlist = g_list_append(luidlist, idp->luid); } } g_free(recuruid); return(luidlist); } GList *sync_get_recur_uids(char *luid,sync_object_type objtype, sync_pair *handle) { GList *list = handle->iddb; GList *uidlist = NULL; guint len = g_list_length(list); guint t; char *recurluid; recurluid = g_strdup_printf("SYNCRECUR-%s", luid); for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp && idp->uid && idp->luid && objtype == idp->object_type && !strcmp(recurluid,idp->luid)) { uidlist = g_list_append(uidlist, idp->uid); } } g_free(recurluid); return(uidlist); } // Get the LUID for the oldest entry not in the changelist char* sync_get_oldest_luid(sync_object_type objtype, GList *changelist, sync_pair *handle) { GList *list = handle->iddb; guint len = 0; gint t; idpair *oldidp = 0; char *enddate = 0; len = g_list_length(list); for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp->enddate && objtype == idp->object_type && (!enddate || strcmp(enddate,idp->enddate)>0)) { GList *changes = changelist; gboolean found = FALSE; while (changes && !found) { changed_object *obj = changes->data; if (obj && obj->uid && !strcmp(obj->uid, idp->luid)) found = TRUE; changes = changes->next; } if (!found) { // OK, this is old and not in the change list enddate = idp->enddate; oldidp = idp; } } } if (oldidp) return(oldidp->luid); return(0); } // Get the UID for the oldest entry not in the changelist char* sync_get_oldest_uid(sync_object_type objtype, GList *changelist, sync_pair *handle) { GList *list = handle->iddb; guint len = g_list_length(list); guint t; idpair *oldidp = 0; char *enddate = 0; for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp && idp->enddate && objtype == idp->object_type && (!enddate || strcmp(enddate,idp->enddate)>0)) { GList *changes = changelist; gboolean found = FALSE; while (changes && !found) { changed_object *obj = changes->data; if (obj && obj->uid && !strcmp(obj->uid, idp->uid)) found = TRUE; changes = changes->next; } if (!found) { // OK, this is old and not in the change list enddate = idp->enddate; oldidp = idp; } } } if (oldidp) return(oldidp->uid); return(0); } char* sync_get_uid(char *luid, sync_object_type objtype, sync_pair *handle) { GList *list = handle->iddb; guint len = g_list_length(list); guint t; for (t = 0; t < len; t++) { idpair *idp; idp = g_list_nth_data(list, t); if (idp && objtype == idp->object_type && idp->uid && idp->luid && !strcmp(luid,idp->luid)) { return(idp->uid); } } return(0); } gboolean sync_is_luid_fake_recur(char *luid, sync_object_type objtype, sync_pair *handle) { char *uid; char realuid[128]; uid = sync_get_uid(luid, objtype, handle); if (uid && sscanf(uid, "SYNCRECUR-%128s", realuid)) return(TRUE); return(FALSE); } int sync_get_nopairs(sync_pair *handle) { return(g_list_length(handle->iddb)); } void sync_insert_idpair(char *uid, char *luid, sync_object_type objtype, char *removepriority, sync_recur_type recur, sync_pair *handle) { idpair *idp = g_malloc0(sizeof(idpair)); g_assert(idp); if (recur == SYNC_RECUR_UID) idp->uid = g_strdup_printf("SYNCRECUR-%s", uid); else { idp->uid = g_strdup(uid); // If defined already, remove sync_delete_idpair(idp->uid, NULL, objtype, handle); } if (recur == SYNC_RECUR_LUID) idp->luid = g_strdup_printf("SYNCRECUR-%s", luid); else { idp->luid = g_strdup(luid); // If defined already, remove sync_delete_idpair(NULL, idp->luid, objtype, handle); } if (removepriority) idp->enddate = g_strdup(removepriority); idp->object_type = objtype; handle->iddb = g_list_append(handle->iddb ,idp); sync_save_ids(handle->iddb, handle); } void sync_delete_all_idpairs(sync_pair *handle, sync_object_type objtypes) { guint t; gboolean removed = FALSE; for (t = 0; t < g_list_length(handle->iddb); t++) { idpair *idp; idp = g_list_nth_data(handle->iddb, t); if (idp && (objtypes & idp->object_type)) { g_free(idp->uid); g_free(idp->luid); g_free(idp); handle->iddb = g_list_remove(handle->iddb, idp); removed = TRUE; t--; } } if (removed) sync_save_ids(handle->iddb, handle); } // Delete all idpairs with either matching UID or matching LUID void sync_delete_idpair(char *uid, char *luid, sync_object_type objtype, sync_pair *handle) { guint t; gboolean removed = FALSE; for (t = 0; t < g_list_length(handle->iddb); t++) { idpair *idp; idp = g_list_nth_data(handle->iddb, t); if (idp && idp->uid && idp->luid && objtype == idp->object_type && ((uid && !strcmp(uid,idp->uid)) || (luid && !strcmp(luid,idp->luid)))) { g_free(idp->uid); g_free(idp->luid); g_free(idp); handle->iddb = g_list_remove(handle->iddb, idp); removed = TRUE; t--; } } if (removed) sync_save_ids(handle->iddb, handle); return; } void sync_msg_init(sync_msg_port *msg) { msg->msg_queue = 0; msg->msg_status = 0; msg->msg_signal = g_cond_new(); msg->msg_mutex = g_mutex_new(); } sync_pair* sync_init(sync_pair *pair) { pair->iddb = sync_load_ids(pair); sync_msg_init(&pair->msg_callret); sync_msg_init(&pair->msg_objchange); return(pair); } int sync_connect_plugin(sync_pair *thissync, connection_type type, client_connection **conn) { int ret; if (*conn) return(0); if (type == CONNECTION_TYPE_LOCAL) async_set_pairlist_status(thissync, "Connecting to first client...", 1); else async_set_pairlist_status(thissync, "Connecting to second client...", 1); *conn = (client_connection*) CALL_PLUGIN_ASYNC((type==CONNECTION_TYPE_LOCAL? thissync->localclient:thissync->remoteclient),"sync_connect", (thissync, type, thissync->object_types)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0) { *conn = NULL; } return(ret); } void sync_disconnect_plugin(sync_pair *thissync, connection_type type, client_connection **conn) { int ret; if (!(*conn)) return; if (type == CONNECTION_TYPE_LOCAL) CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect", (conn)); else CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect", (conn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); *conn = NULL; } char *sync_error_string(sync_msg_type err) { char *ret = NULL; switch(err) { case SYNC_MSG_REQFAILED: ret = "Unknown error"; break; case SYNC_MSG_CONNECTIONERROR: ret = "Connection error"; break; case SYNC_MSG_MODIFYERROR: ret = "Failed to modify entry"; break; case SYNC_MSG_PROTOCOLERROR: ret = "Protocol error"; break; case SYNC_MSG_NORIGHTSERROR: ret = "Not authorized"; break; case SYNC_MSG_DONTEXISTERROR: ret = "File does not exist"; break; case SYNC_MSG_DATABASEFULLERROR: ret = "Database is full"; break; case SYNC_MSG_USERDEFERRED: ret = "User refused"; break; default: ret = "No error"; } return(ret); } gpointer sync_main(gpointer data) { sync_pair *thissync = data; client_connection *remoteconn = NULL; client_connection *localconn = NULL; gboolean localalwaysconn = FALSE; gboolean remotealwaysconn = FALSE; gboolean localfeedthrough = FALSE; gboolean remotefeedthrough = FALSE; int ret = 0, cmd = SYNC_MSG_OBJECTCHANGE; int lastsyncmissed = 0; gpointer cmddata = NULL; int feedthroughentries = 0; gboolean feedthroughsyncing = FALSE; thissync->thread_running = TRUE; if (!thissync->localclient || !thissync->remoteclient) { async_set_pairlist_status(thissync, "Plugin missing.", 0); cmd = SYNC_MSG_QUIT; } sync_init(thissync); if (thissync->localclient==0) { fprintf(stderr, "The local client [%s] has failed to load!\n", thissync->localname); return NULL; } localalwaysconn = (gboolean) CALL_PLUGIN(thissync->localclient, "always_connected",()); if (localalwaysconn) { localconn = (client_connection*) CALL_PLUGIN_ASYNC(thissync->localclient, "sync_connect", (thissync, CONNECTION_TYPE_LOCAL, thissync->object_types)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0) { dd(printf("Could not connect first client.\n")); cmd = SYNC_MSG_QUIT; } if (localconn) localfeedthrough = localconn->is_feedthrough; } remotealwaysconn = (gboolean) CALL_PLUGIN(thissync->remoteclient, "always_connected",()); if (remotealwaysconn) { remoteconn = (client_connection*) CALL_PLUGIN_ASYNC(thissync->remoteclient,"sync_connect", (thissync, CONNECTION_TYPE_REMOTE, thissync->object_types)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0) { dd(printf("Could not connect second client.\n")); cmd = SYNC_MSG_QUIT; } if (remoteconn) remotefeedthrough = remoteconn->is_feedthrough; } while(cmd != SYNC_MSG_QUIT) { change_info *remote_changes = NULL, *local_changes = NULL; GTimeVal gt; lastsyncmissed = thissync->syncmissed; thissync->syncmissed = 0; if (((cmd == SYNC_MSG_OBJECTCHANGE && !thissync->manualonly) || cmd == SYNC_MSG_FORCESYNC || cmd == SYNC_MSG_FORCERESYNC) && (localfeedthrough || remotefeedthrough)) { // If we are only fed through, send message if (localfeedthrough) { CALL_PLUGIN_ASYNC(thissync->localclient,"resp_objchanged", (localconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); } if (remotefeedthrough) { CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_objchanged", (remoteconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); } cmd = 0; } // Don't sync if syncinterval is set to manual // unless we do it manually :) if ((cmd == SYNC_MSG_OBJECTCHANGE && !thissync->manualonly) || cmd == SYNC_MSG_FORCESYNC || cmd == SYNC_MSG_FORCERESYNC) { if (!remotealwaysconn) { async_set_pairlist_status(thissync, "Connecting to second client...", 1); remoteconn = (client_connection*) CALL_PLUGIN_ASYNC(thissync->remoteclient,"sync_connect", (thissync, CONNECTION_TYPE_REMOTE, thissync->object_types)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0) { remoteconn = NULL; thissync->syncmissed = 1; LOG_ERROR("Failed to connect remote"); } } if (remoteconn) { if (!localalwaysconn) { async_set_pairlist_status(thissync, "Connecting to first client...", 1); localconn = (client_connection*) CALL_PLUGIN_ASYNC(thissync->localclient,"sync_connect", (thissync, CONNECTION_TYPE_LOCAL, thissync->object_types)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0) { localconn = NULL; thissync->syncmissed = 1; LOG_ERROR("Failed to connect local"); } } if (localconn) { sync_object_type newdbs = 0; if (cmd == SYNC_MSG_FORCERESYNC) newdbs = SYNC_OBJECT_TYPE_ANY; async_set_pairlist_status(thissync, "Getting second plugin changes...", 1); CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes", (remoteconn,newdbs)); ret = sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &remote_changes); if (ret == SYNC_MSG_CONNECTIONERROR) thissync->syncmissed = 1; if (ret >= 0 && remote_changes) { newdbs |= remote_changes->newdbs; dd(printf("New DBs: %d\n", newdbs)); async_set_pairlist_status(thissync, "Getting first plugin changes...", 1); CALL_PLUGIN_ASYNC(thissync->localclient, "get_changes", (localconn,newdbs)); ret=sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &local_changes); if (ret == SYNC_MSG_CONNECTIONERROR) thissync->syncmissed = 1; if (ret >= 0 && local_changes) { if (local_changes->newdbs & (~newdbs)) { newdbs |= local_changes->newdbs; // Refetch remote databases which have been reset locally sync_free_change_info(remote_changes); remote_changes = NULL; async_set_pairlist_status(thissync, "Getting full second database...", 1); CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes", (remoteconn,newdbs)); ret = sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &remote_changes); } if (ret >= 0) { // We got changes successfully if (thissync->playwelcomesound && thissync->welcomesound && lastsyncmissed) { // Play welcome sound if we were unreachable for a while gnome_sound_play(thissync->welcomesound); } sync_process_changes(localconn, remoteconn, local_changes->changes, remote_changes->changes, newdbs, thissync); } else { LOG_ERROR("Failed to get full second changelist"); } } else { LOG_ERROR("Failed to get first plugin changes"); } if (!localalwaysconn) { CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect",(localconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); localconn = NULL; } } else { LOG_ERROR("Failed to get second plugin changes"); } if (local_changes) sync_free_change_info(local_changes); local_changes = NULL; if (remote_changes) sync_free_change_info(remote_changes); remote_changes = NULL; } if (!remotealwaysconn) { CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect", (remoteconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); remoteconn = NULL; } } } // Specific LOCAL and REMOTE to know who's calling if (cmd == SYNC_MSG_GET_LOCAL_CHANGES) { sync_object_type newdbs = (sync_object_type) cmddata; // A remote feedthrough plugin asks to get changes ret = sync_connect_plugin(thissync, CONNECTION_TYPE_LOCAL, &localconn); async_set_pairlist_status(thissync, "Getting first plugin changes...", 1); local_changes = NULL; if (localconn) { CALL_PLUGIN_ASYNC(thissync->localclient, "get_changes", (localconn,newdbs)); ret = sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &local_changes); if (ret < 0) local_changes = NULL; } if (local_changes) feedthroughentries = g_list_length(local_changes->changes); else feedthroughentries = 0; if (ret >= 0) feedthroughsyncing = TRUE; async_set_pairlist_status(thissync, "Synchronizing...", 1); // Send the local change list to the remote plugin CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_get_changes", (remoteconn, ret, local_changes)); ret = sync_wait_msg(&thissync->msg_callret, NULL); sync_free_change_info(local_changes); local_changes = NULL; } if (cmd == SYNC_MSG_GET_REMOTE_CHANGES) { sync_object_type newdbs = (sync_object_type) cmddata; // A local feedthrough plugin asks to get changes ret = sync_connect_plugin(thissync, CONNECTION_TYPE_REMOTE, &remoteconn); async_set_pairlist_status(thissync, "Getting second plugin changes...", 1); remote_changes = NULL; if (remoteconn) { CALL_PLUGIN_ASYNC(thissync->remoteclient, "get_changes", (remoteconn,newdbs)); ret = sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &remote_changes); if (ret < 0) remote_changes = NULL; } if (remote_changes) feedthroughentries = g_list_length(remote_changes->changes); else feedthroughentries = 0; if (ret >= 0) feedthroughsyncing = TRUE; async_set_pairlist_status(thissync, "Synchronizing...", 1); // Send the remote change list to the local plugin CALL_PLUGIN_ASYNC(thissync->localclient,"resp_get_changes", (localconn, ret, remote_changes)); ret = sync_wait_msg(&thissync->msg_callret, NULL); sync_free_change_info(remote_changes); remote_changes = NULL; } if (cmd == SYNC_MSG_LOCAL_MODIFY) { GList *results = NULL; // A remote feedthrough plugin asks to do a modify ret = sync_connect_plugin(thissync, CONNECTION_TYPE_LOCAL, &localconn); local_changes = NULL; if (localconn) { results = sync_do_syncobj_modifies(thissync, localconn, thissync->localclient, cmddata); ret = 0; sync_free_changes(cmddata); } if (results) // Should be only successful feedthroughentries += g_list_length(results); CALL_PLUGIN_ASYNC(thissync->remoteclient,"resp_modify", (remoteconn, ret, results)); ret=sync_wait_msg(&thissync->msg_callret, NULL); } if (cmd == SYNC_MSG_REMOTE_MODIFY) { GList *results = NULL; // A local feedthrough plugin asks to do a modify ret = sync_connect_plugin(thissync, CONNECTION_TYPE_REMOTE, &remoteconn); remote_changes = NULL; if (remoteconn) { results = sync_do_syncobj_modifies(thissync, remoteconn, thissync->remoteclient, cmddata); ret = 0; sync_free_changes(cmddata); } if (results) // Should be only successful feedthroughentries += g_list_length(results); CALL_PLUGIN_ASYNC(thissync->localclient,"resp_modify", (localconn, ret, results)); ret=sync_wait_msg(&thissync->msg_callret, NULL); } if (cmd == SYNC_MSG_LOCAL_SYNCDONE) { if (localconn) { gboolean success = TRUE; success = ((gboolean) cmddata); CALL_PLUGIN_ASYNC(thissync->localclient, "sync_done", (localconn,success)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (cmddata) sync_log_sync_success(feedthroughentries, thissync); if (!localalwaysconn) { sync_disconnect_plugin(thissync, CONNECTION_TYPE_LOCAL, &localconn); } feedthroughsyncing = FALSE; } } if (cmd == SYNC_MSG_REMOTE_SYNCDONE) { if (remoteconn) { gboolean success = TRUE; success = ((gboolean) cmddata); CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_done", (remoteconn,success)); ret = sync_wait_msg(&thissync->msg_callret, NULL); if (cmddata) sync_log_sync_success(feedthroughentries, thissync); if (!remotealwaysconn) { sync_disconnect_plugin(thissync, CONNECTION_TYPE_REMOTE,&remoteconn); } feedthroughsyncing = FALSE; } } if (!thissync->manualonly) { if (thissync->syncmissed) { async_set_pairlist_status(thissync, "Searching for client...",0); dd(printf("Searching for units...\n")); g_get_current_time(>); gt.tv_sec+=thissync->reconninterval; } else { if (!feedthroughsyncing) { async_set_pairlist_status(thissync, "Waiting for change.",0); dd(printf("Waiting for change...\n")); } g_get_current_time(>); gt.tv_sec+=thissync->syncinterval; } if (thissync->syncinterval) cmd = sync_wait_msg_data(&thissync->msg_objchange, >, &cmddata); else cmd = sync_wait_msg_data(&thissync->msg_objchange, NULL, &cmddata); } else { async_set_pairlist_status(thissync, "Press \"Sync\" to synchronize.",0); cmd = sync_wait_msg_data(&thissync->msg_objchange, NULL, &cmddata); } dd(printf("Got message %d\n", cmd)); if (cmd == SYNC_MSG_OBJECTCHANGE) { if (thissync->dwelltime) { ret = 0; do { async_set_pairlist_status(thissync, "Change detected, waiting for more...",1); dd(printf("Dwelling...\n")); g_get_current_time(>); gt.tv_sec+=thissync->dwelltime; } while ((ret = sync_wait_msg_data(&thissync->msg_objchange, >, &cmddata)) && ret == SYNC_MSG_OBJECTCHANGE); if (ret && ret != SYNC_MSG_OBJECTCHANGE) { // Refeed unexpected message sync_send_msg_data(&thissync->msg_objchange, ret, cmddata); } } } if (!cmd) cmd = SYNC_MSG_OBJECTCHANGE; } dd(printf("Syncthread: Exiting.\n")); if (localconn) { CALL_PLUGIN_ASYNC(thissync->localclient, "sync_disconnect", (localconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); } if (remoteconn) { CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_disconnect", (remoteconn)); ret = sync_wait_msg(&thissync->msg_callret, NULL); } thissync->thread_running = FALSE; if (thissync->callback) g_idle_add(thissync->callback, thissync->callbackdata); return(NULL); } void sync_free_change_info(change_info *change) { if (change) { sync_free_changes(change->changes); g_free(change); } } changed_object* sync_copy_changed_object(changed_object *change) { changed_object* c = g_malloc0(sizeof(changed_object)); if (change->comp) c->comp = g_strdup(change->comp); if (change->uid) c->uid = g_strdup(change->uid); if (change->removepriority) c->removepriority = g_strdup(change->removepriority); c->change_type = change->change_type; c->object_type = change->object_type; return(c); } void sync_free_changed_object(changed_object *change) { if (!change) return; if (change->comp) g_free(change->comp); if (change->removepriority) g_free(change->removepriority); if (change->uid) g_free(change->uid); g_free(change); } void sync_free_changes(GList *changes) { while (changes) { GList *change; changed_object *obj; change = g_list_first(changes); if (change->data) { obj = change->data; sync_free_changed_object(obj); } changes = g_list_remove(changes, change->data); } } void sync_print_changes(GList *changes) { GList *ch = changes; while (ch) { changed_object *obj = ch->data; if (obj) { dd(printf("Change type: %d, object type: %d\n", obj->change_type, obj->object_type)); if (obj->uid) dd(printf("UID: \n%s\n", obj->uid)); if (obj->comp) dd(printf("Comp: \n%s\n", obj->comp)); } ch = ch->next; } } GList* sync_adjust_capacity(GList *change_list, GList **extra, int fromlocal, client_connection *otherconn, sync_plugin* otherplugin, sync_object_type objtype, sync_pair *thissync) { float full = 0.0; int ret; int loops = 20; // Max deletes int records=0, *maxrecords=NULL; int deltachange = 0; GList *change; if (!otherconn->managedbsize) return(change_list); // How many additions and deletions are allready in the list? change = change_list; while (change) { changed_object *obj = change->data; if (obj && obj->object_type == objtype) { if (obj->change_type == SYNC_OBJ_HARDDELETED || obj->change_type == SYNC_OBJ_SOFTDELETED) deltachange--; if ((obj->change_type == SYNC_OBJ_ADDED || obj->change_type == SYNC_OBJ_MODIFIED) && (!obj->uid)) deltachange++; } change = change->next; } switch(objtype) { case SYNC_OBJECT_TYPE_CALENDAR: records = (otherconn->calendarrecords); maxrecords = &(otherconn->maxcalendarrecords); break; case SYNC_OBJECT_TYPE_TODO: records = (otherconn->todorecords); maxrecords = &(otherconn->maxtodorecords); break; case SYNC_OBJECT_TYPE_PHONEBOOK: records = (otherconn->phonebookrecords); maxrecords = &(otherconn->maxphonebookrecords); break; } records += deltachange; if (maxrecords && *maxrecords > 0) { full=(((float) records)/ ((float) *maxrecords)); dd(printf("List fullness: %f %d %d\n", full, records, *maxrecords)); while (full > 0.75 && loops-- > 0) { // 75% full store char *delluid; delluid = fromlocal?sync_get_oldest_luid(objtype,change_list,thissync): sync_get_oldest_uid(objtype,change_list,thissync); if (delluid) { dd(printf("Delete %s for space reasons.\n", delluid)); change_list = sync_add_to_change_list(change_list, NULL, delluid, NULL, SYNC_OBJ_SOFTDELETED, objtype, extra, NULL, SYNC_RECUR_NONE); records--; full=(((float) records)/ ((float) *maxrecords)); } else full = 0.0; } } return(change_list); } GList* sync_add_to_change_list(GList *list, char* comp, char* uid, char* removepriority, int change_type, sync_object_type object_type, GList **extra, char *origuid, sync_recur_type recur) { changed_object* c = g_malloc0(sizeof(changed_object)); if (comp) c->comp = g_strdup(comp); if (uid) c->uid = g_strdup(uid); if (removepriority) c->removepriority = g_strdup(removepriority); c->change_type = change_type; c->object_type = object_type; if (extra) { change_list_extra* e = g_malloc0(sizeof(change_list_extra)); if (origuid) e->origuid = g_strdup(origuid); e->recur = recur; *extra = g_list_append(*extra, e); } return(g_list_append(list, c)); } void sync_free_modify_results(GList *results) { while (results) { syncobj_modify_result *result = results->data; if (result->returnuid) g_free(result->returnuid); g_free(result); results = g_list_remove(results, results->data); } } // Send a modification list to the plugin (either one at a time or the // full list) and return a list of syncobj_modify_result's. GList* sync_do_syncobj_modifies(sync_pair *thissync, client_connection *conn, sync_plugin* plugin, GList *change_list) { int ret = 0; GList *results = NULL; dd(printf("Got %d changes.\n", g_list_length(change_list))); sync_print_changes(change_list); if (dlsym(plugin->plugin,"syncobj_modify_list")) { // Send list in one batch (new API function) async_set_pairlist_status(thissync, "Synchronizing...", 1); CALL_PLUGIN_ASYNC(plugin, "syncobj_modify_list", (conn, change_list)); ret=sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &results); } else { // Send list command by command (normal API) char msg[256]; GList *change; change = change_list; async_set_pairlist_status(thissync, "Synchronizing...", 1); while(change) { changed_object *obj = change->data; syncobj_modify_result *result = g_malloc0(sizeof(syncobj_modify_result)); ret = SYNC_MSG_NOMSG; if (obj->change_type == SYNC_OBJ_HARDDELETED || obj->change_type == SYNC_OBJ_SOFTDELETED) { CALL_PLUGIN_ASYNC(plugin, "syncobj_delete", (conn, obj->uid, obj->object_type, obj->change_type == SYNC_OBJ_SOFTDELETED)); ret=sync_wait_msg(&thissync->msg_callret, NULL); } if (obj->change_type == SYNC_OBJ_ADDED || obj->change_type == SYNC_OBJ_MODIFIED) { char outluid[256]; int outluidlen = 256; CALL_PLUGIN_ASYNC(plugin, "syncobj_modify", (conn, obj->comp, obj->uid, obj->object_type, outluid, &outluidlen)); ret=sync_wait_msg(&thissync->msg_callret, NULL); if (ret < 0 && obj->uid) { outluidlen = 256; g_free(obj->uid); obj->uid = NULL; CALL_PLUGIN_ASYNC(plugin, "syncobj_modify", (conn, obj->comp, NULL, obj->object_type, outluid, &outluidlen)); ret=sync_wait_msg(&thissync->msg_callret, NULL); } if (ret >= 0 && outluidlen > 0 && !obj->uid) result->returnuid = g_strdup(outluid); } result->result = ret; results = g_list_append(results, result); change = change->next; } } return(results); } void sync_process_changes(client_connection *localconn, client_connection *remoteconn, GList *localchanges, GList *remotechanges, sync_object_type newdbs, sync_pair *thissync) { int t,n, ret; int count = 0, totcount = 0, actualcount = 0; int fromlocal; gboolean success = TRUE; sync_compare_result compare; if (newdbs) sync_delete_all_idpairs(thissync, newdbs); // Remove outdated ID pairs if new database dd(printf("Process: %d %d\n", g_list_length(remotechanges), g_list_length(localchanges))); thissync->default_duplicate_action = thissync->duplicate_mode; // Check for double remote-local modifies of the same object for (t = 0; t < g_list_length(localchanges); t++) { for (n = 0; n < g_list_length(remotechanges); n++) { changed_object *robj, *lobj; char *luid, *uid; sync_compare_result compare; robj = g_list_nth_data(remotechanges, n); lobj = g_list_nth_data(localchanges, t); if (robj->uid && lobj->uid && robj->object_type == lobj->object_type && lobj->change_type != SYNC_OBJ_FILTERED && robj->change_type != SYNC_OBJ_FILTERED) { luid = sync_get_luid(lobj->uid, lobj->object_type, thissync); if (luid && !strcmp(robj->uid,luid)) { compare = sync_compare_objects(robj->comp, lobj->comp, lobj->object_type); // Double modify (or slow sync). Check of objects are equal. if (compare == SYNC_COMPARE_EQUAL){ // They are equal, so just don't synchronize. robj->change_type = SYNC_OBJ_FILTERED; lobj->change_type = SYNC_OBJ_FILTERED; dd(printf("Old changed object %s is equal to %s. No sync.\n", lobj->uid, robj->uid)); } else { sync_duplicate_action action = SYNC_DUPLICATE_ACTION_KEEPBOTH; if (thissync->default_duplicate_action == SYNC_DUPLICATE_ACTION_NONE) { sync_ask_duplicate(lobj, robj, TRUE, thissync); sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &action); } else action = thissync->default_duplicate_action; if (action == SYNC_DUPLICATE_ACTION_KEEPBOTH) sync_delete_idpair(lobj->uid, NULL, lobj->object_type, thissync); else if (action == SYNC_DUPLICATE_ACTION_KEEPFIRST) robj->change_type = SYNC_OBJ_FILTERED; else lobj->change_type = SYNC_OBJ_FILTERED; } } /*if (!luid) { uid = sync_get_uid(robj->uid, robj->object_type, thissync); if (!uid) {*/ // None of the two objects has a UID connection // If they are equal, just connect them compare = sync_compare_objects(robj->comp, lobj->comp, lobj->object_type); if (compare == SYNC_COMPARE_EQUAL) { sync_insert_idpair(lobj->uid, robj->uid, lobj->object_type, lobj->removepriority, SYNC_RECUR_NONE, thissync); robj->change_type = SYNC_OBJ_FILTERED; lobj->change_type = SYNC_OBJ_FILTERED; dd(printf("New changed object %s is equal to %s. Merging.\n", lobj->uid, robj->uid)); } else { sync_duplicate_action action = SYNC_DUPLICATE_ACTION_KEEPBOTH; if (compare == SYNC_COMPARE_SIMILAR) { if (thissync->default_duplicate_action == SYNC_DUPLICATE_ACTION_NONE) { sync_ask_duplicate(lobj, robj, FALSE, thissync); sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &action); } else action = thissync->default_duplicate_action; } if (action == SYNC_DUPLICATE_ACTION_KEEPFIRST) { robj->change_type = SYNC_OBJ_FILTERED; sync_insert_idpair(lobj->uid, robj->uid, lobj->object_type, lobj->removepriority, SYNC_RECUR_NONE, thissync); } else if (action == SYNC_DUPLICATE_ACTION_KEEPSECOND) { lobj->change_type = SYNC_OBJ_FILTERED; sync_insert_idpair(lobj->uid, robj->uid, robj->object_type, robj->removepriority, SYNC_RECUR_NONE, thissync); } /* } }*/ } } } } totcount = g_list_length(remotechanges)+g_list_length(localchanges); for (fromlocal = 1; fromlocal >=0 ; fromlocal--) { sync_plugin *myplugin, *otherplugin; GList *currentchanges; client_connection *myconn, *otherconn; sync_direction dir; GList *change_list = NULL; GList *change_extra = NULL; GList *results = NULL; if (fromlocal) { currentchanges = localchanges; myconn = localconn; otherconn = remoteconn; myplugin = thissync->localclient; otherplugin = thissync->remoteclient; dir = SYNC_DIR_LOCALTOREMOTE; } else { currentchanges = remotechanges; myconn = remoteconn; otherconn = localconn; myplugin = thissync->remoteclient; otherplugin = thissync->localclient; dir = SYNC_DIR_REMOTETOLOCAL; } // Loop twice, first process local changes, then remote changes. for (t = 0; success && t < g_list_length(currentchanges); t++) { changed_object *obj; char *luid = NULL; char newluid[256]; int newluidlen = 256; int filt; obj = g_list_nth_data(currentchanges, t); if (obj->uid) { luid = fromlocal?sync_get_luid(obj->uid,obj->object_type, thissync): sync_get_uid(obj->uid,obj->object_type, thissync); if (!luid) { // Since both VEVENT and VTODO can be in a VCALENDAR, // some apps may not be able to tell them apart. // => Try the other type. if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) { luid = fromlocal?sync_get_luid(obj->uid, SYNC_OBJECT_TYPE_TODO, thissync): sync_get_uid(obj->uid,obj->object_type, thissync); if (luid) obj->object_type = SYNC_OBJECT_TYPE_TODO; } else if (obj->object_type == SYNC_OBJECT_TYPE_TODO) { luid = fromlocal?sync_get_luid(obj->uid, SYNC_OBJECT_TYPE_CALENDAR, thissync): sync_get_uid(obj->uid,obj->object_type, thissync); if (luid) obj->object_type = SYNC_OBJECT_TYPE_CALENDAR; } } } else { luid = NULL; } //printf("Change, UID: %s\n", obj->uid); //printf("LUID: %s\n", luid); //printf("Type: %d\n", obj->change_type); //if (obj->object_type == SYNC_OBJECT_TYPE_PHONEBOOK) //printf("Phonebook entry:\n%s\n", obj->comp); //if (obj->object_type == SYNC_OBJECT_TYPE_CALENDAR) //printf("Calendar entry:\n%s\n", obj->comp); // See if the event should be filtered out for (filt = 0; filt < g_list_length(thissync->filters); filt++) { sync_filter *filter = g_list_nth_data(thissync->filters, filt); if ((filter->type & obj->object_type) && (filter->dir & dir) && (obj->object_type <= SYNC_OBJECT_TYPE_TODO) && obj->comp) { // Type and direction are right char *fielddata = sync_get_key_data(obj->comp, filter->field); gboolean match = FALSE; if (fielddata && filter->data) { char *pos = fielddata; while (pos) { char *nextword = strstr(pos, ","); if (nextword) { nextword[0] = 0; nextword++; } if (!g_strcasecmp(pos, filter->data)) match = TRUE; pos = nextword; } g_free(fielddata); } if (filter->rule == SYNC_RULE_NONE) obj->change_type = SYNC_OBJ_FILTERED; if (filter->rule == SYNC_RULE_ALLBUT && match) obj->change_type = SYNC_OBJ_FILTERED; if (filter->rule == SYNC_RULE_ONLYIF && !match) obj->change_type = SYNC_OBJ_FILTERED; } } if (luid && (obj->change_type == SYNC_OBJ_SOFTDELETED || obj->change_type == SYNC_OBJ_HARDDELETED)) { // This entry exists in remote db. Delete it. actualcount++; if (obj->change_type == SYNC_OBJ_HARDDELETED) { GList *recurs; // Deleted not only for space reasons. change_list = sync_add_to_change_list(change_list, NULL, luid, NULL, obj->change_type, obj->object_type, &change_extra, obj->uid, SYNC_RECUR_NONE); // If there are faked recurs, delete them as well recurs = fromlocal?sync_get_recur_luids(obj->uid,obj->object_type, thissync): sync_get_recur_uids(obj->uid,obj->object_type, thissync); // Remove old faked recurrance entries in remote db while(recurs) { GList *recur; recur = g_list_first(recurs); change_list = sync_add_to_change_list(change_list, NULL, recur->data, NULL, SYNC_OBJ_HARDDELETED, obj->object_type, &change_extra, obj->uid, fromlocal?SYNC_RECUR_UID: SYNC_RECUR_LUID); recurs = g_list_remove(recurs, recur->data); } } //sync_delete_idpair(fromlocal?obj->uid:NULL, // !fromlocal?obj->uid:NULL,obj->object_type, thissync); } if (obj->change_type == SYNC_OBJ_ADDED || obj->change_type == SYNC_OBJ_MODIFIED) { int t; GList *recurs; actualcount++; // This entry is new or modified. if (!luid) { // A new entry is to be added, check db capacity change_list = sync_adjust_capacity(change_list,&change_extra, fromlocal,otherconn,otherplugin, obj->object_type,thissync); } change_list = sync_add_to_change_list(change_list, obj->comp, luid, obj->removepriority, obj->change_type, obj->object_type, &change_extra, obj->uid, SYNC_RECUR_NONE); if (otherconn->fake_recurring) { recurs = fromlocal?sync_get_recur_luids(obj->uid,obj->object_type, thissync): sync_get_recur_uids(obj->uid,obj->object_type, thissync); // Remove old faked recurrance entries in remote db while(recurs) { GList *recur; recur = g_list_first(recurs); change_list = sync_add_to_change_list(change_list, NULL, recur->data, NULL, SYNC_OBJ_HARDDELETED, obj->object_type, &change_extra, obj->uid, SYNC_RECUR_NONE); recurs = g_list_remove(recurs, recur->data); } CALL_PLUGIN_ASYNC(myplugin, "syncobj_get_recurring", (myconn, obj)); ret=sync_wait_msg_data(&thissync->msg_callret, NULL, (gpointer*) &recurs); if (ret>=0) { // This object is recurring, fake entries in remote db for (t=0; t < g_list_length(recurs); t++) { changed_object *recurobj; recurobj = g_list_nth_data(recurs, t); change_list = sync_adjust_capacity(change_list,&change_extra, fromlocal,otherconn, otherplugin, obj->object_type, thissync); change_list = sync_add_to_change_list(change_list, recurobj->comp, NULL, recurobj->removepriority, SYNC_OBJ_ADDED, obj->object_type, &change_extra, obj->uid, fromlocal?SYNC_RECUR_UID: SYNC_RECUR_LUID); } sync_free_changes(recurs); } } } } // Send change_list to plugin, either in batch or as separate commands. results = sync_do_syncobj_modifies(thissync, otherconn, otherplugin, change_list); if (results) { // Update the UID-LUID database and check success int n; gboolean databasefullasked = FALSE; for (n = 0; n < g_list_length(change_list) && success; n++) { changed_object *obj = NULL; syncobj_modify_result *result = NULL; change_list_extra *extra = NULL; obj = g_list_nth_data(change_list, n); result = g_list_nth_data(results, n); extra = g_list_nth_data(change_extra, n); if (result) ret = result->result; if (obj && result && extra && ret >= 0) { if (obj->change_type == SYNC_OBJ_HARDDELETED || obj->change_type == SYNC_OBJ_SOFTDELETED) { sync_delete_idpair(!fromlocal?obj->uid:NULL, fromlocal?obj->uid:NULL, obj->object_type, thissync); } if ((obj->change_type == SYNC_OBJ_ADDED || obj->change_type == SYNC_OBJ_MODIFIED) && (result->returnuid)) { sync_insert_idpair(fromlocal?extra->origuid:result->returnuid, !fromlocal?extra->origuid:result->returnuid, obj->object_type, obj->removepriority, extra->recur, thissync); } } if (ret == SYNC_MSG_CONNECTIONERROR) success = FALSE; if (ret == SYNC_MSG_DATABASEFULLERROR && !databasefullasked) { int questret = 0; sync_ask_dbfull(obj, thissync); questret = sync_wait_msg(&thissync->msg_callret, NULL); if (questret == SYNC_MSG_FALSE) success = FALSE; // Try again else databasefullasked = TRUE; } } sync_free_modify_results(results); results = NULL; } // Free change list sync_free_changes(change_list); change_list = NULL; // Free extra info list while (change_extra) { change_list_extra *extra = change_extra->data; if (extra->origuid) g_free(extra->origuid); g_free(extra); change_extra = g_list_remove(change_extra, change_extra->data); } } if (success) { sync_log_sync_success(actualcount, thissync); } else { async_add_pairlist_log(thissync, "Synchronization failed.", SYNC_LOG_ERROR); dd(printf("Synchronization failed!\n")); } CALL_PLUGIN_ASYNC(thissync->localclient, "sync_done", (localconn,success)); ret = sync_wait_msg(&thissync->msg_callret, NULL); CALL_PLUGIN_ASYNC(thissync->remoteclient, "sync_done", (remoteconn,success)); ret = sync_wait_msg(&thissync->msg_callret, NULL); } void sync_log_sync_success(int actualcount, sync_pair *thissync) { char *log; dd(printf("Synchronization success!\n")); if (actualcount > 0) { if (thissync->playsyncsound && thissync->syncsound) gnome_sound_play(thissync->syncsound); if (actualcount > 1) log = g_strdup_printf("Synchronization of %d entries succeeded.", actualcount); else log = g_strdup_printf("Synchronization of one entry succeeded."); async_add_pairlist_log(thissync, log, SYNC_LOG_SUCCESS); g_free(log); } else { async_add_pairlist_log(thissync, "Connection OK; no new changes reported.", SYNC_LOG_SUCCESS); } } // Send a message with both type and data. The data is NOT copied. void sync_send_msg_data(sync_msg_port *port, int type, gpointer data) { sync_msg *msg; g_mutex_lock(port->msg_mutex); msg = g_malloc0(sizeof(sync_msg)); g_assert(msg); msg->type = type; msg->data = data; port->msg_queue = g_list_append(port->msg_queue, msg); port->msg_status=1; g_cond_signal(port->msg_signal); g_mutex_unlock(port->msg_mutex); } void sync_send_msg(sync_msg_port *port, int type) { sync_send_msg_data(port, type, NULL); } // If the message is on the queue already, dont add this too void sync_send_msg_once(sync_msg_port *port, int type) { sync_msg *msg; GList *q; g_mutex_lock(port->msg_mutex); q = port->msg_queue; while (q) { msg = q->data; if (msg && msg->type == type) { port->msg_status=1; g_cond_signal(port->msg_signal); g_mutex_unlock(port->msg_mutex); return; } q = q->next; } msg = g_malloc0(sizeof(sync_msg)); g_assert(msg); msg->type = type; msg->data = NULL; port->msg_queue = g_list_append(port->msg_queue, msg); port->msg_status=1; g_cond_signal(port->msg_signal); g_mutex_unlock(port->msg_mutex); } int sync_wait_msg_data(sync_msg_port *port, GTimeVal *untiltime, gpointer *data) { GList *entry; sync_msg *msg; int ret = 0; g_mutex_lock(port->msg_mutex); while(!(entry = g_list_first(port->msg_queue))) { if (untiltime) { while (port->msg_status==0) { ret=g_cond_timed_wait(port->msg_signal, port->msg_mutex, untiltime); if (!ret) { port->msg_status=0; g_mutex_unlock(port->msg_mutex); return(0); } } port->msg_status=0; } else { while (port->msg_status==0) { g_cond_wait(port->msg_signal, port->msg_mutex); } port->msg_status=0; } } msg = entry->data; if (msg) { ret = msg->type; if (data) *data = msg->data; g_free(msg); } port->msg_queue = g_list_remove_link(port->msg_queue, entry); g_mutex_unlock(port->msg_mutex); return(ret); } int sync_wait_msg(sync_msg_port *port, GTimeVal *untiltime) { return(sync_wait_msg_data(port, untiltime, NULL)); } void sync_feedthrough_get_changes(sync_pair *thissync, connection_type type, sync_object_type newdbs) { if (type == CONNECTION_TYPE_LOCAL) sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_GET_REMOTE_CHANGES, (gpointer) newdbs); else sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_GET_LOCAL_CHANGES, (gpointer) newdbs); } void sync_feedthrough_syncdone(sync_pair *thissync, connection_type type, gboolean success) { if (type == CONNECTION_TYPE_LOCAL) sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_REMOTE_SYNCDONE, (gpointer) success); else sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_LOCAL_SYNCDONE, (gpointer) success); } void sync_feedthrough_modify(sync_pair *thissync, connection_type type, GList *modify) { if (type == CONNECTION_TYPE_LOCAL) sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_REMOTE_MODIFY, modify); else sync_send_msg_data(&thissync->msg_objchange, SYNC_MSG_LOCAL_MODIFY, modify); } void sync_object_changed(sync_pair *thissync) { sync_send_msg_once(&thissync->msg_objchange, SYNC_MSG_OBJECTCHANGE); } void sync_force_sync(sync_pair *thissync) { sync_send_msg_once(&thissync->msg_objchange, SYNC_MSG_FORCESYNC); } void sync_force_resync(sync_pair *thissync) { sync_send_msg(&thissync->msg_objchange, SYNC_MSG_FORCERESYNC); } void sync_settings_changed(sync_pair *thissync) { sync_send_msg(&thissync->msg_objchange, SYNC_MSG_SETTINGSCHANGED); } void sync_quit(sync_pair *thissync, gboolean (*callback)(gpointer), gpointer data) { thissync->callback = callback; thissync->callbackdata = data; sync_send_msg(&thissync->msg_objchange, SYNC_MSG_QUIT); } void sync_set_requestdata(gpointer data,sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = NULL; sync_send_msg_data(&thissync->msg_callret, SYNC_MSG_REQDONE, data); } void sync_set_requestdatamsg(gpointer data, sync_msg_type type, sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = NULL; sync_send_msg_data(&thissync->msg_callret, type, data); } void sync_set_requestdone(sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = NULL; sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQDONE); } gpointer sync_set_requestfailed(sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = NULL; sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQFAILED); return(NULL); } gpointer sync_set_requestfailederror(char* errorstr, sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = g_strdup(errorstr); // Non-queued error string, sync_send_msg(&thissync->msg_callret, SYNC_MSG_REQFAILED); return(NULL); } void sync_set_requestmsg(sync_msg_type type, sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = NULL; sync_send_msg(&thissync->msg_callret, type); } void sync_set_requestmsgerror(sync_msg_type type, char *errorstr, sync_pair *thissync) { if (thissync->errorstr) g_free(thissync->errorstr); thissync->errorstr = g_strdup(errorstr); // Non-queued error string, // doesn't really matter sync_send_msg(&thissync->msg_callret, type); } // Wrapper for the gui function async_add... void sync_log(sync_pair *pair, char* logstring, sync_log_type type) { async_add_pairlist_log(pair, logstring, type); } char *name_extension(char *name) { int n = strlen(name)-1; while (n>=0 && name[n]!='.') n--; if (n < 0) return(NULL); return(name+n+1); } void read_pluginlist() { DIR *dir; char *ptr; if ((dir = opendir(PLUGINDIR))) { struct dirent *de = NULL; while ((de = readdir(dir))) { char name[256]; gpointer mod; char *ext; sync_plugin *plugin; strncpy(name, de->d_name,255); ext = name_extension(name); if (ext && !strcmp(ext, "so")) { char *path; path = g_strdup_printf("%s/%s", PLUGINDIR, name); dd(printf("Trying %s...\n", path)); mod = dlopen(path, RTLD_LAZY); g_free(path); if (mod && dlsym(mod,"short_name")) { sync_plugin tmp_plugin; int APIver = 0; tmp_plugin.plugin = mod; APIver = (int) CALL_PLUGIN((&tmp_plugin), "plugin_API_version", ()); if (APIver != MULTISYNC_API_VER) { char *name = CALL_PLUGIN((&tmp_plugin), "long_name", ()); char *msg; if (!name) name = CALL_PLUGIN((&tmp_plugin), "short_name", ()); msg = g_strdup_printf("The plugin \"%s\" cannot be loaded\n" "since it is of another version than " "MultiSync itself.\n" "Please install the correct " "version or remove the plugin.", name); sync_async_msg(msg); g_free(msg); if (mod) dlclose(mod); } else { plugin = g_malloc0(sizeof(sync_plugin)); g_assert(plugin); plugin->plugin = mod; dlerror(); /* Clear previous errors */ plugin->shortname = CALL_PLUGIN(plugin, "short_name", ()); ptr=dlerror(); if (ptr != 0) { printf("ERROR: Failed to load plugin: %s! (Error=%s, missing short_name-symbol)\n", name, ptr); dlclose(mod); continue; } plugin->longname = CALL_PLUGIN(plugin, "long_name", ()); CALL_PLUGIN(plugin, "plugin_init", ()); dd(printf("Plugin found: %s\n", plugin->longname)); pluginlist = g_list_append(pluginlist, plugin); } } else { dd(printf("Failed to load plugin: %s (Error=%s)\n", name, dlerror())); } } } closedir(dir); } } sync_plugin* get_plugin_by_name(char *name) { int n; for (n = 0; n < g_list_length(pluginlist); n++) { sync_plugin *plugin = g_list_nth_data(pluginlist, n); if (plugin && !strcmp(plugin->shortname, name)) return(plugin); } async_set_multisync_status("One or more plugins not found."); return(NULL); } sync_pair *clone_syncpair(sync_pair *orig) { sync_pair *pair; int n; pair = g_malloc0(sizeof(sync_pair)); memcpy(pair, orig, sizeof(sync_pair)); pair->localname = g_strdup(orig->localname); pair->remotename = g_strdup(orig->remotename); pair->datapath = g_strdup(orig->datapath); if (orig->errorstr) pair->errorstr = g_strdup(orig->errorstr); if (orig->syncsound) pair->syncsound = g_strdup(orig->syncsound); if (orig->welcomesound) pair->welcomesound = g_strdup(orig->welcomesound); if (orig->displayname) pair->displayname = g_strdup(orig->displayname); if (orig->status) pair->status = g_strdup(orig->status); pair->original = orig; pair->log = NULL; for (n = 0; n < g_list_length(orig->log); n++) { sync_pair_log *log = g_list_nth_data(orig->log, n); sync_pair_log *newlog = g_malloc0(sizeof(sync_pair_log)); memcpy(newlog, log, sizeof(sync_pair_log)); newlog->logstring = g_strdup(log->logstring); pair->log = g_list_append(pair->log, newlog); } pair->filters = NULL; for (n = 0; n < g_list_length(orig->filters); n++) { sync_filter *filter = g_list_nth_data(orig->filters, n); sync_filter *newfilter = g_malloc0(sizeof(sync_filter)); memcpy(newfilter, filter, sizeof(sync_filter)); newfilter->field = g_strdup(filter->field); newfilter->data = g_strdup(filter->data); pair->filters = g_list_append(pair->filters, newfilter); } return(pair); } void apply_syncpair(sync_pair *orig, sync_pair* newpair) { if (orig->localname) g_free(orig->localname); if (orig->remotename) g_free(orig->remotename); if (orig->datapath) g_free(orig->datapath); while (orig->log) { GList *first = g_list_first(orig->log); sync_pair_log *log = first->data; if (log->logstring) g_free(log->logstring); g_free(log); orig->log = g_list_remove(orig->log, log); } if (orig->errorstr) g_free(orig->errorstr); if (orig->syncsound) g_free(orig->syncsound); if (orig->status) g_free(orig->status); while (orig->filters) { GList *first = g_list_first(orig->filters); sync_filter *filter = first->data; if (filter->field) g_free(filter->field); if (filter->data) g_free(filter->data); g_free(filter); orig->filters = g_list_remove(orig->filters, filter); } if (orig->welcomesound) g_free(orig->welcomesound); if (orig->displayname) g_free(orig->displayname); memcpy(orig, newpair, sizeof(sync_pair)); g_free(newpair); save_syncpair(orig); } void free_syncpair(sync_pair *pair) { if (pair->localname) g_free(pair->localname); if (pair->remotename) g_free(pair->remotename); if (pair->datapath) g_free(pair->datapath); while (pair->log) { GList *first = g_list_first(pair->log); sync_pair_log *log = first->data; if (log->logstring) g_free(log->logstring); g_free(log); pair->log = g_list_remove(pair->log, log); } if (pair->errorstr) g_free(pair->errorstr); if (pair->syncsound) g_free(pair->syncsound); if (pair->welcomesound) g_free(pair->welcomesound); if (pair->displayname) g_free(pair->displayname); if (pair->status) g_free(pair->status); while (pair->filters) { GList *first = g_list_first(pair->filters); sync_filter *filter = first->data; if (filter->field) g_free(filter->field); if (filter->data) g_free(filter->data); g_free(filter); pair->filters = g_list_remove(pair->filters, filter); } g_free(pair); } void save_syncpair(sync_pair *pair) { if (pair->datapath) { char *filename; FILE *f; filename = g_strdup_printf("%s/%s", pair->datapath, "syncpair"); if ((f = fopen(filename, "w"))) { int n; if (pair->remotename) fprintf(f, "remoteclient = %s\n", pair->remotename); if (pair->localname) fprintf(f, "localclient = %s\n", pair->localname); fprintf(f, "syncinterval = %d\n", pair->syncinterval); fprintf(f, "dwelltime = %d\n", pair->dwelltime); fprintf(f, "manualonly = %s\n", pair->manualonly?"yes":"no"); fprintf(f, "reconninterval = %d\n", pair->reconninterval); fprintf(f, "object_types = 0x%x\n", pair->object_types); if (pair->syncsound) fprintf(f, "syncsound = %s\n", pair->syncsound); if (pair->welcomesound) fprintf(f, "welcomesound = %s\n", pair->welcomesound); if (pair->displayname) fprintf(f, "displayname = %s\n", pair->displayname); fprintf(f, "playsyncsound = %s\n", pair->playsyncsound?"yes":"no"); fprintf(f, "playwelcomesound = %s\n", pair->playsyncsound?"yes":"no"); for (n = 0; n < g_list_length(pair->filters); n++) { sync_filter *filter = g_list_nth_data(pair->filters, n); if (filter->field && filter->data) fprintf(f, "filter = %d,%d,%d,\"%s\",\"%s\"\n", filter->type, filter->dir, filter->rule, (filter->field && strlen(filter->field)>0)?filter->field:"*", (filter->data && strlen(filter->data)>0)?filter->data:"*"); } fprintf(f, "duplicatemode = %d\n", pair->duplicate_mode); fclose(f); } g_free(filename); } } void read_syncpairs() { DIR *dir; if (!(dir = opendir(datadir))) { if (!mkdir(datadir,S_IRWXU)) dir = opendir(datadir); } if (dir) { struct dirent *de; while ((de = readdir(dir))) { gpointer mod; sync_pair *pair; struct stat stbuf; char *dirname = NULL; dirname = g_strdup_printf ("%s/%s", datadir, de->d_name); if((stat(dirname, &stbuf) == 0) && ((stbuf.st_mode & S_IFMT) == S_IFDIR)) { char *filename; FILE *f; char line[256]; filename = g_strdup_printf ("%s/%s/%s", datadir, de->d_name, "syncpair"); if ((f = fopen(filename, "r"))) { pair = g_malloc0(sizeof(sync_pair)); g_assert(pair); pair->object_types = 0xffff; sscanf(de->d_name, "%d", &pair->pairno); pair->datapath = g_strdup_printf ("%s/%s", datadir, de->d_name); while (fgets(line, 256, f)) { char prop[128], data[256]; if (sscanf(line, "%128s = %256[^\n]", prop, data) == 2) { if (!strcmp(prop, "localclient")) { int n; pair->localname = g_strdup(data); pair->localclient = get_plugin_by_name(data); } if (!strcmp(prop, "remoteclient")) { pair->remotename = g_strdup(data); pair->remoteclient = get_plugin_by_name(data); } if (!strcmp(prop, "syncinterval")) { sscanf(data, "%d", &pair->syncinterval); } if (!strcmp(prop, "dwelltime")) { sscanf(data, "%d", &pair->dwelltime); } if (!strcmp(prop, "manualonly")) { if (!strcmp(data, "yes")) pair->manualonly = TRUE; else pair->manualonly = FALSE; } if (!strcmp(prop, "duplicatemode")) { sscanf(data, "%d", &pair->duplicate_mode); } if (!strcmp(prop, "reconninterval")) { sscanf(data, "%d", &pair->reconninterval); } if (!strcmp(prop, "object_types")) { sscanf(data, "0x%x", &pair->object_types); } if (!strcmp(prop, "playsyncsound")) { if (!strcmp(data, "yes")) pair->playsyncsound = TRUE; else pair->playsyncsound = FALSE; } if (!strcmp(prop, "playwelcomesound")) { if (!strcmp(data, "yes")) pair->playwelcomesound = TRUE; else pair->playwelcomesound = FALSE; } if (!strcmp(prop, "syncsound")) { pair->syncsound = g_strdup(data); } if (!strcmp(prop, "welcomesound")) { pair->welcomesound = g_strdup(data); } if (!strcmp(prop, "displayname")) { pair->displayname = g_strdup(data); } if (!strcmp(prop, "filter")) { int type, dir, rule; char field[256], content[256]; if (sscanf(data, "%d,%d,%d,\"%255[^\"]\",\"%255[^\"]\"", &type, &dir, &rule, field, content) >= 5) { sync_filter *filter = g_malloc0(sizeof(sync_filter)); filter->type = type; filter->dir = dir; filter->rule = rule; if (!(strlen(field) == 1 && field[0] == '*')) filter->field = g_strdup(field); if (!(strlen(content) == 1 && content[0] == '*')) filter->data = g_strdup(content); pair->filters = g_list_append(pair->filters, filter); } } } } dd(printf("Found pair: %s - %s\n", pair->localname, pair->remotename)); syncpairlist = g_list_append(syncpairlist, pair); fclose(f); } g_free(filename); } g_free(dirname); } closedir(dir); } } char* sync_objtype_as_string(sync_object_type objtype) { return(objtype==SYNC_OBJECT_TYPE_CALENDAR?"Event": (objtype==SYNC_OBJECT_TYPE_TODO?"ToDo": (objtype==SYNC_OBJECT_TYPE_PHONEBOOK?"Contact":"Unknown"))); } void sig_child(void) { waitpid(-1, NULL, WNOHANG); } int main (int argc, char **argv) { char *dir; int n; int nogui = 0; char *optdatadir = NULL; GConfClient *gconf; struct poptOption popts[] = { { "nogui", 'q', POPT_ARG_NONE, &nogui, 1, "Do not show the GUI. Useful for starting " "MultiSync in a script.", NULL }, { "datadir", 'd', POPT_ARG_STRING, &optdatadir, 1, "Use a non-default data direcory.", NULL }, NULL }; struct sigaction sg_action; #ifdef ENABLE_NLS bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR); textdomain (PACKAGE); #endif sg_action.sa_handler = (void *)sig_child; sg_action.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &sg_action, NULL); gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE, argc, argv, GNOME_PARAM_POPT_TABLE, popts, GNOME_PARAM_APP_DATADIR, PACKAGE_DATA_DIR, GNOME_PARAM_NONE); bonobo_init(&argc, argv); if (optdatadir) { if (*optdatadir == '/') { datadir = g_strdup(optdatadir); } else { datadir = g_strdup_printf ("%s/%s", g_get_home_dir (), optdatadir); } } else { datadir = g_strdup_printf ("%s/%s", g_get_home_dir (), DATADIR); } gtk_set_locale (); gtk_init (&argc, &argv); gconf = gconf_client_get_default (); gconf_client_add_dir (gconf, SYNC_GCONF_PATH, GCONF_CLIENT_PRELOAD_NONE, NULL); if (!nogui) nogui = gconf_client_get_bool (gconf, SYNC_GCONF_PATH"/no_gui", NULL); // Initialize sound gnome_sound_init("localhost"); if (getenv ("MULTISYNC_DEBUG")) multisync_debug = TRUE; if (optdatadir || sync_open_process_socket()) { async_set_multisync_status("Multisync running."); read_pluginlist(); read_syncpairs(); for (n = 0; n < g_list_length(syncpairlist); n++) { sync_pair *pair = g_list_nth_data(syncpairlist, n); pthread_create(&pair->syncthread, NULL, sync_main, pair); } init_tray(); if (!nogui) { gtk_idle_add(open_mainwindow, NULL); } bonobo_main (); } g_free(datadir); return 0; } char* sync_get_datapath(sync_pair *pair) { return(pair->datapath); }