Bumped copyright dates for 2013
[barry.git] / opensync-plugin / src / barry_sync.cc
blobfe19cca579271034970602c581384abb8aaf2c1c
1 //
2 // \file barry_sync.cc
3 // Opensync module for the USB Blackberry handheld
4 //
6 /*
7 Copyright (C) 2006-2013, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include <opensync/opensync.h>
23 #include <barry/barry.h>
24 #include <barry/dll.h>
25 #include "barry_sync.h"
26 #include "environment.h"
27 #include "vevent.h"
28 #include "vcard.h"
29 #include "trace.h"
30 #include "config.h"
31 #include "i18n.h"
32 #include <string>
33 #include <glib.h>
34 #include <stdint.h>
35 #include <stdlib.h>
36 #include <string.h>
38 // All functions that are callable from outside must look like C
39 extern "C" {
40 static void *initialize(OSyncMember *member, OSyncError **error);
41 static void connect(OSyncContext *ctx);
42 static void get_changeinfo(OSyncContext *ctx);
43 static void sync_done(OSyncContext *ctx);
44 static void disconnect(OSyncContext *ctx);
45 static void finalize(void *data);
46 BXEXPORT void get_info(OSyncEnv *env);
50 //////////////////////////////////////////////////////////////////////////////
52 // Support functions and classes
56 void GetChanges(OSyncContext *ctx, BarryEnvironment *env,
57 DatabaseSyncState *pSync,
58 const char *DBDBName,
59 const char *ObjTypeName, const char *FormatName,
60 GetData_t getdata)
62 Trace trace("GetChanges");
64 // shortcut references
65 using namespace Barry;
66 using Barry::RecordStateTable;
67 Mode::Desktop &desktop = *env->GetDesktop();
69 // find the matching cache, state table, and id map for this change
70 DatabaseSyncState::cache_type &cache = pSync->m_Cache;
71 idmap &map = pSync->m_IdMap;
73 // check if slow sync has been requested, and if so, empty the
74 // cache and id map and start fresh
75 if( osync_member_get_slow_sync(env->member, ObjTypeName) ) {
76 trace.log(_("GetChanges: slow sync request detected, clearing cache and id map"));
77 cache.clear();
78 map.clear();
81 // fetch state table
82 unsigned int dbId = desktop.GetDBID(DBDBName);
83 RecordStateTable &table = pSync->m_Table;
84 desktop.GetRecordStateTable(dbId, table);
86 // cycle through the state table...
87 // - if not in cache, it is added.
88 // - if in cache, check Blackberry's dirty flag
89 RecordStateTable::StateMapType::const_iterator i = table.StateMap.begin();
90 for( ; i != table.StateMap.end(); ++i ) {
92 OSyncChange *change = 0;
93 const RecordStateTable::IndexType &index = i->first;
94 const RecordStateTable::State &state = i->second;
96 // search the idmap for the UID
97 std::string uid = pSync->Map2Uid(state.RecordId);
99 // search the cache
100 DatabaseSyncState::cache_type::const_iterator c = cache.find(state.RecordId);
101 if( c == cache.end() ) {
102 // not in cache, this is a new item
103 trace.log(_("found an ADDED change"));
104 change = osync_change_new();
105 osync_change_set_changetype(change, CHANGE_ADDED);
107 else {
108 // in the cache... dirty?
109 if( state.Dirty ) {
110 // modified
111 trace.log(_("found a MODIFIED change"));
112 change = osync_change_new();
113 osync_change_set_changetype(change, CHANGE_MODIFIED);
115 else {
116 trace.log(_("no change detected"));
120 // finish filling out the change object
121 if( change ) {
122 osync_change_set_member(change, env->member);
123 osync_change_set_objformat_string(change, FormatName);
125 osync_change_set_uid(change, uid.c_str());
126 trace.logf(_("change record ID: %s"), uid.c_str());
128 // Now you can set the data for the object
129 // Set the last argument to FALSE if the real data
130 // should be queried later in a "get_data" function
131 char *data = (*getdata)(env, dbId, index);
132 osync_change_set_data(change, data, strlen(data), TRUE);
134 // just report the change via
135 osync_context_report_change(ctx, change);
137 // map our IDs for later
138 map.Map(uid, state.RecordId);
142 // now cycle through the cache... any objects in the cache
143 // but not found in the state table means that they have been
144 // deleted in the device
145 DatabaseSyncState::cache_type::const_iterator c = cache.begin();
146 for( ; c != cache.end(); ++c ) {
147 uint32_t recordId = c->first;
149 // search the idmap for the UID
150 std::string uid = pSync->Map2Uid(recordId);
152 // search the state table
153 i = table.StateMap.begin();
154 for( ; i != table.StateMap.end(); ++i ) {
156 if( i->second.RecordId == recordId )
157 break; // found
160 // check if not found...
161 if( i == table.StateMap.end() ) {
162 // register a DELETE, no data
163 trace.log(_("found DELETE change"));
165 OSyncChange *change = osync_change_new();
166 osync_change_set_changetype(change, CHANGE_DELETED);
167 osync_change_set_member(change, env->member);
168 osync_change_set_objformat_string(change, FormatName);
170 osync_change_set_uid(change, uid.c_str());
171 trace.log(uid.c_str());
173 // report the change
174 osync_context_report_change(ctx, change);
178 // finally, cycle through the state map again, and overwrite the
179 // cache with the current state table. Memory only... if successful,
180 // it will be written back to disk later on.
182 // start fresh
183 cache.clear();
185 for( i = table.StateMap.begin(); i != table.StateMap.end(); ++i ) {
186 const RecordStateTable::State &state = i->second;
187 cache[state.RecordId] = false;
191 CommitData_t GetCommitFunction(OSyncChange *change)
193 OSyncObjType *type = osync_change_get_objtype(change);
194 const char *name = osync_objtype_get_name(type);
195 if( strcmp(name, "event") == 0 ) {
196 return &VEventConverter::CommitRecordData;
198 else if( strcmp(name, "contact") == 0 ) {
199 return &VCardConverter::CommitRecordData;
201 else {
202 return 0;
206 bool FinishSync(OSyncContext *ctx, BarryEnvironment *env, DatabaseSyncState *pSync)
208 Trace trace("FinishSync()");
210 if( !pSync->m_Sync ) {
211 // this mode is disabled in config, skip
212 return true;
215 Barry::Mode::Desktop &desktop = *env->GetDesktop();
217 // get the state table again, so we can update
218 // the cache properly
219 desktop.GetRecordStateTable(pSync->m_dbId, pSync->m_Table);
221 // update the cache
222 if( !pSync->SaveCache() ) {
223 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
224 _("Error saving calendar cache"));
225 return false;
228 // save the id map
229 pSync->CleanupMap();
230 if( !pSync->SaveMap() ) {
231 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
232 _("Error saving calendar id map"));
233 return false;
236 // clear all dirty flags in device
237 env->ClearDirtyFlags(pSync->m_Table, pSync->m_dbName);
238 return true;
243 //////////////////////////////////////////////////////////////////////////////
245 // OpenSync API
248 static void *initialize(OSyncMember *member, OSyncError **error)
250 Trace trace("initialize");
252 BarryEnvironment *env = 0;
254 // Create the environment struct, including our Barry objects
255 try {
256 env = new BarryEnvironment(member);
258 // Load config file for this plugin
259 char *configdata;
260 int configsize;
261 if (!osync_member_get_config(member, &configdata, &configsize, error)) {
262 osync_error_update(error, _("Unable to get config data: %s"),
263 osync_error_print(error));
264 delete env;
265 return NULL;
268 // Process the configdata here and set the options on your environment
269 env->ParseConfig(configdata, configsize);
270 free(configdata);
272 // FIXME - near the end of release, do a run with
273 // this set to true, and look for USB protocol
274 // inefficiencies.
275 Barry::Init(env->m_DebugMode);
277 // Load all needed cache files
278 if( env->m_CalendarSync.m_Sync ) {
279 env->m_CalendarSync.LoadCache();
280 env->m_CalendarSync.LoadMap();
283 if( env->m_ContactsSync.m_Sync ) {
284 env->m_ContactsSync.LoadCache();
285 env->m_ContactsSync.LoadMap();
288 return env;
291 // Don't let C++ exceptions escape to the C code
292 catch( std::bad_alloc &ba ) {
293 osync_error_update(error, _("Unable to allocate memory for environment: %s"), ba.what());
294 delete env;
295 return NULL;
297 catch( std::exception &e ) {
298 osync_error_update(error, "%s", e.what());
299 delete env;
300 return NULL;
304 static void connect(OSyncContext *ctx)
306 Trace trace("connect");
308 try {
310 // Each time you get passed a context (which is used to track
311 // calls to your plugin) you can get the data your returned in
312 // initialize via this call:
313 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
315 // Probe for available devices
316 Barry::Probe probe;
317 int nIndex = probe.FindActive(env->m_pin);
318 if( nIndex == -1 ) {
319 osync_context_report_error(ctx, OSYNC_ERROR_NO_CONNECTION, _("Unable to find PIN %lx"), env->m_pin);
320 return;
323 env->Connect(probe.Get(nIndex));
325 // Success!
326 osync_context_report_success(ctx);
329 // Don't let exceptions escape to the C modules
330 catch( std::bad_alloc &ba ) {
331 osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION,
332 _("Unable to allocate memory for controller: %s"), ba.what());
334 catch( std::exception &e ) {
335 osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION,
336 "%s", e.what());
340 static void get_changeinfo(OSyncContext *ctx)
342 Trace trace("get_changeinfo");
344 try {
346 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
347 OSyncMember *member = osync_context_get_member(ctx);
349 if( env->m_CalendarSync.m_Sync && osync_member_objtype_enabled(member, "event") ) {
350 GetChanges(ctx, env, &env->m_CalendarSync,
351 "Calendar", "event", "vevent20",
352 &VEventConverter::GetRecordData);
355 if( env->m_ContactsSync.m_Sync && osync_member_objtype_enabled(member, "contact") ) {
356 GetChanges(ctx, env, &env->m_ContactsSync,
357 "Address Book", "contact", "vcard30",
358 &VCardConverter::GetRecordData);
361 // Success!
362 osync_context_report_success(ctx);
364 // don't let exceptions escape to the C modules
365 catch( std::exception &e ) {
366 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
370 static osync_bool commit_change(OSyncContext *ctx, OSyncChange *change)
372 Trace trace("commit_change");
374 // We can rely on a valid record state table, since get_changeinfo()
375 // will be called first, and will fill the table.
377 try {
379 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
381 // find the needed commit function, based on objtype of the change
382 CommitData_t CommitData = GetCommitFunction(change);
383 if( !CommitData ) {
384 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
385 _("unable to get commit function pointer"));
386 return false;
389 // find the matching cache, state table, and id map for this change
390 DatabaseSyncState *pSync = env->GetSyncObject(change);
391 if( !pSync ) {
392 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
393 _("unable to get sync object that matches change type"));
394 return false;
397 // is syncing turned on for this type?
398 if( !pSync->m_Sync ) {
399 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
400 _("This object type is disabled in the barry-sync config"));
401 return false;
404 // make references instead of pointers
405 DatabaseSyncState::cache_type &cache = pSync->m_Cache;
406 Barry::RecordStateTable &table = pSync->m_Table;
407 idmap &map = pSync->m_IdMap;
408 Barry::Mode::Desktop &desktop = *env->GetDesktop();
409 unsigned int dbId = pSync->m_dbId;
412 // extract RecordId from change's UID,
413 // and update the ID map if necessary
414 const char *uid = osync_change_get_uid(change);
415 trace.logf("uid from change: %s", uid);
416 if( strlen(uid) == 0 ) {
417 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
418 _("uid from change object is blank!"));
420 unsigned long RecordId = pSync->GetMappedRecordId(uid);
422 // search for the RecordId in the state table, to find the
423 // index... we only need the index if we are deleting or
424 // modifying
425 Barry::RecordStateTable::IndexType StateIndex;
426 if( osync_change_get_changetype(change) != CHANGE_ADDED ) {
427 if( !table.GetIndex(RecordId, &StateIndex) ) {
428 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
429 _("unable to get state table index for RecordId: %lu"),
430 RecordId);
431 return false;
435 std::string errmsg;
436 bool status;
438 switch( osync_change_get_changetype(change) )
440 case CHANGE_DELETED:
441 desktop.DeleteRecord(dbId, StateIndex);
442 cache.erase(RecordId);
443 map.UnmapUid(uid);
444 break;
446 case CHANGE_ADDED:
447 status = (*CommitData)(env, dbId, StateIndex, RecordId,
448 osync_change_get_data(change), true, errmsg);
449 if( !status ) {
450 trace.logf(_("CommitData() for ADDED state returned false: %s"), errmsg.c_str());
451 osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str());
452 map.UnmapUid(uid);
453 return false;
455 cache[RecordId] = false;
456 break;
458 case CHANGE_MODIFIED:
459 status = (*CommitData)(env, dbId, StateIndex, RecordId,
460 osync_change_get_data(change), false, errmsg);
461 if( !status ) {
462 trace.logf(_("CommitData() for MODIFIED state returned false: %s"), errmsg.c_str());
463 osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str());
464 map.UnmapUid(uid);
465 return false;
467 break;
469 default:
470 trace.log(_("Unknown change type"));
471 osync_debug("barry-sync", 0, _("Unknown change type"));
472 break;
475 // Answer the call
476 osync_context_report_success(ctx);
477 return true;
481 catch( std::exception &e ) {
482 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
484 // we don't worry about unmapping ids here, as there
485 // is still a possibility that the record was added...
486 // plus, the map might not get written out to disk anyway
487 // in a plugin error state
489 return false;
493 static void sync_done(OSyncContext *ctx)
496 // This function will only be called if the sync was successfull
499 Trace trace("sync_done");
501 try {
503 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
505 // we reconnect to the device here, since dirty flags
506 // for records we've just touched do not show up until
507 // a disconnect... as far as I can tell.
508 env->Reconnect();
510 // do cleanup for each database
511 if( FinishSync(ctx, env, &env->m_CalendarSync) &&
512 FinishSync(ctx, env, &env->m_ContactsSync) )
514 // Success
515 osync_context_report_success(ctx);
519 catch( std::exception &e ) {
520 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
524 static void disconnect(OSyncContext *ctx)
526 Trace trace("disconnect");
528 // Disconnect the controller, which closes our connection
529 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
530 env->Disconnect();
532 // Done!
533 osync_context_report_success(ctx);
536 static void finalize(void *data)
538 Trace trace("finalize");
540 BarryEnvironment *env = (BarryEnvironment *)data;
541 delete env;
544 void get_info(OSyncEnv *env)
546 Trace trace("get_info");
548 static bool i18n_initialized = false;
549 if( !i18n_initialized ) {
550 // initialize i18n gettext directory
551 // the rest is done in i18n.h
552 setlocale(LC_ALL, "");
553 bindtextdomain(PACKAGE, LOCALEDIR);
555 i18n_initialized = true;
558 // Create first plugin
559 OSyncPluginInfo *info = osync_plugin_new_info(env);
561 // not translation for these strings, as I think they are const,
562 // and the info struct relies on their existence
563 info->name = "barry-sync";
564 info->longname = "Barry OpenSync plugin v" PACKAGE_VERSION " for the Blackberry handheld";
565 info->description = "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
566 info->version = 1; // API version (opensync api?)
567 info->is_threadsafe = TRUE;
569 info->functions.initialize = initialize;
570 info->functions.connect = connect;
571 info->functions.sync_done = sync_done;
572 info->functions.disconnect = disconnect;
573 info->functions.finalize = finalize;
574 info->functions.get_changeinfo = get_changeinfo;
576 // If you like, you can overwrite the default timeouts of your plugin
577 // The default is set to 60 sec. Note that this MUST NOT be used to
578 // wait for expected timeouts (Lets say while waiting for a webserver).
579 // you should wait for the normal timeout and return a error.
580 // info->timeouts.connect_timeout = 5;
581 // There are more timeouts for the other functions
584 // Register each supported feature
587 // Calendar entries, using batch commit
588 osync_plugin_accept_objtype(info, "event");
589 osync_plugin_accept_objformat(info, "event", "vevent20", NULL);
590 osync_plugin_set_commit_objformat(info, "event", "vevent20",
591 commit_change);
593 // Address Book entries
594 osync_plugin_accept_objtype(info, "contact");
595 osync_plugin_accept_objformat(info, "contact", "vcard30", NULL);
596 osync_plugin_set_commit_objformat(info, "contact", "vcard30",
597 commit_change);