lib: changed asserts to exceptions, to guarantee these are never used
[barry.git] / opensync-plugin / src / barry_sync.cc
blobdda3bf8ebee28716e66b57525d80e4847389dde7
1 //
2 // \file barry_sync.cc
3 // Opensync module for the USB Blackberry handheld
4 //
6 /*
7 Copyright (C) 2006-2012, 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 <string>
32 #include <glib.h>
33 #include <stdint.h>
34 #include <stdlib.h>
35 #include <string.h>
37 // All functions that are callable from outside must look like C
38 extern "C" {
39 static void *initialize(OSyncMember *member, OSyncError **error);
40 static void connect(OSyncContext *ctx);
41 static void get_changeinfo(OSyncContext *ctx);
42 static void sync_done(OSyncContext *ctx);
43 static void disconnect(OSyncContext *ctx);
44 static void finalize(void *data);
45 BXEXPORT void get_info(OSyncEnv *env);
49 //////////////////////////////////////////////////////////////////////////////
51 // Support functions and classes
55 void GetChanges(OSyncContext *ctx, BarryEnvironment *env,
56 DatabaseSyncState *pSync,
57 const char *DBDBName,
58 const char *ObjTypeName, const char *FormatName,
59 GetData_t getdata)
61 Trace trace("GetChanges");
63 // shortcut references
64 using namespace Barry;
65 using Barry::RecordStateTable;
66 Mode::Desktop &desktop = *env->GetDesktop();
68 // find the matching cache, state table, and id map for this change
69 DatabaseSyncState::cache_type &cache = pSync->m_Cache;
70 idmap &map = pSync->m_IdMap;
72 // check if slow sync has been requested, and if so, empty the
73 // cache and id map and start fresh
74 if( osync_member_get_slow_sync(env->member, ObjTypeName) ) {
75 trace.log("GetChanges: slow sync request detected, clearing cache and id map");
76 cache.clear();
77 map.clear();
80 // fetch state table
81 unsigned int dbId = desktop.GetDBID(DBDBName);
82 RecordStateTable &table = pSync->m_Table;
83 desktop.GetRecordStateTable(dbId, table);
85 // cycle through the state table...
86 // - if not in cache, it is added.
87 // - if in cache, check Blackberry's dirty flag
88 RecordStateTable::StateMapType::const_iterator i = table.StateMap.begin();
89 for( ; i != table.StateMap.end(); ++i ) {
91 OSyncChange *change = 0;
92 const RecordStateTable::IndexType &index = i->first;
93 const RecordStateTable::State &state = i->second;
95 // search the idmap for the UID
96 std::string uid = pSync->Map2Uid(state.RecordId);
98 // search the cache
99 DatabaseSyncState::cache_type::const_iterator c = cache.find(state.RecordId);
100 if( c == cache.end() ) {
101 // not in cache, this is a new item
102 trace.log("found an ADDED change");
103 change = osync_change_new();
104 osync_change_set_changetype(change, CHANGE_ADDED);
106 else {
107 // in the cache... dirty?
108 if( state.Dirty ) {
109 // modified
110 trace.log("found a MODIFIED change");
111 change = osync_change_new();
112 osync_change_set_changetype(change, CHANGE_MODIFIED);
114 else {
115 trace.log("no change detected");
119 // finish filling out the change object
120 if( change ) {
121 osync_change_set_member(change, env->member);
122 osync_change_set_objformat_string(change, FormatName);
124 osync_change_set_uid(change, uid.c_str());
125 trace.logf("change record ID: %s", uid.c_str());
127 // Now you can set the data for the object
128 // Set the last argument to FALSE if the real data
129 // should be queried later in a "get_data" function
130 char *data = (*getdata)(env, dbId, index);
131 osync_change_set_data(change, data, strlen(data), TRUE);
133 // just report the change via
134 osync_context_report_change(ctx, change);
136 // map our IDs for later
137 map.Map(uid, state.RecordId);
141 // now cycle through the cache... any objects in the cache
142 // but not found in the state table means that they have been
143 // deleted in the device
144 DatabaseSyncState::cache_type::const_iterator c = cache.begin();
145 for( ; c != cache.end(); ++c ) {
146 uint32_t recordId = c->first;
148 // search the idmap for the UID
149 std::string uid = pSync->Map2Uid(recordId);
151 // search the state table
152 i = table.StateMap.begin();
153 for( ; i != table.StateMap.end(); ++i ) {
155 if( i->second.RecordId == recordId )
156 break; // found
159 // check if not found...
160 if( i == table.StateMap.end() ) {
161 // register a DELETE, no data
162 trace.log("found DELETE change");
164 OSyncChange *change = osync_change_new();
165 osync_change_set_changetype(change, CHANGE_DELETED);
166 osync_change_set_member(change, env->member);
167 osync_change_set_objformat_string(change, FormatName);
169 osync_change_set_uid(change, uid.c_str());
170 trace.log(uid.c_str());
172 // report the change
173 osync_context_report_change(ctx, change);
177 // finally, cycle through the state map again, and overwrite the
178 // cache with the current state table. Memory only... if successful,
179 // it will be written back to disk later on.
181 // start fresh
182 cache.clear();
184 for( i = table.StateMap.begin(); i != table.StateMap.end(); ++i ) {
185 const RecordStateTable::State &state = i->second;
186 cache[state.RecordId] = false;
190 CommitData_t GetCommitFunction(OSyncChange *change)
192 OSyncObjType *type = osync_change_get_objtype(change);
193 const char *name = osync_objtype_get_name(type);
194 if( strcmp(name, "event") == 0 ) {
195 return &VEventConverter::CommitRecordData;
197 else if( strcmp(name, "contact") == 0 ) {
198 return &VCardConverter::CommitRecordData;
200 else {
201 return 0;
205 bool FinishSync(OSyncContext *ctx, BarryEnvironment *env, DatabaseSyncState *pSync)
207 Trace trace("FinishSync()");
209 if( !pSync->m_Sync ) {
210 // this mode is disabled in config, skip
211 return true;
214 Barry::Mode::Desktop &desktop = *env->GetDesktop();
216 // get the state table again, so we can update
217 // the cache properly
218 desktop.GetRecordStateTable(pSync->m_dbId, pSync->m_Table);
220 // update the cache
221 if( !pSync->SaveCache() ) {
222 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
223 "Error saving calendar cache");
224 return false;
227 // save the id map
228 pSync->CleanupMap();
229 if( !pSync->SaveMap() ) {
230 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
231 "Error saving calendar id map");
232 return false;
235 // clear all dirty flags in device
236 env->ClearDirtyFlags(pSync->m_Table, pSync->m_dbName);
237 return true;
242 //////////////////////////////////////////////////////////////////////////////
244 // OpenSync API
247 static void *initialize(OSyncMember *member, OSyncError **error)
249 Trace trace("initialize");
251 BarryEnvironment *env = 0;
253 // Create the environment struct, including our Barry objects
254 try {
255 env = new BarryEnvironment(member);
257 // Load config file for this plugin
258 char *configdata;
259 int configsize;
260 if (!osync_member_get_config(member, &configdata, &configsize, error)) {
261 osync_error_update(error, "Unable to get config data: %s",
262 osync_error_print(error));
263 delete env;
264 return NULL;
267 // Process the configdata here and set the options on your environment
268 env->ParseConfig(configdata, configsize);
269 free(configdata);
271 // FIXME - near the end of release, do a run with
272 // this set to true, and look for USB protocol
273 // inefficiencies.
274 Barry::Init(env->m_DebugMode);
276 // Load all needed cache files
277 if( env->m_CalendarSync.m_Sync ) {
278 env->m_CalendarSync.LoadCache();
279 env->m_CalendarSync.LoadMap();
282 if( env->m_ContactsSync.m_Sync ) {
283 env->m_ContactsSync.LoadCache();
284 env->m_ContactsSync.LoadMap();
287 return env;
290 // Don't let C++ exceptions escape to the C code
291 catch( std::bad_alloc &ba ) {
292 osync_error_update(error, "Unable to allocate memory for environment: %s", ba.what());
293 delete env;
294 return NULL;
296 catch( std::exception &e ) {
297 osync_error_update(error, "%s", e.what());
298 delete env;
299 return NULL;
303 static void connect(OSyncContext *ctx)
305 Trace trace("connect");
307 try {
309 // Each time you get passed a context (which is used to track
310 // calls to your plugin) you can get the data your returned in
311 // initialize via this call:
312 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
314 // Probe for available devices
315 Barry::Probe probe;
316 int nIndex = probe.FindActive(env->m_pin);
317 if( nIndex == -1 ) {
318 osync_context_report_error(ctx, OSYNC_ERROR_NO_CONNECTION, "Unable to find PIN %lx", env->m_pin);
319 return;
322 env->Connect(probe.Get(nIndex));
324 // Success!
325 osync_context_report_success(ctx);
328 // Don't let exceptions escape to the C modules
329 catch( std::bad_alloc &ba ) {
330 osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION,
331 "Unable to allocate memory for controller: %s", ba.what());
333 catch( std::exception &e ) {
334 osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION,
335 "%s", e.what());
339 static void get_changeinfo(OSyncContext *ctx)
341 Trace trace("get_changeinfo");
343 try {
345 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
346 OSyncMember *member = osync_context_get_member(ctx);
348 if( env->m_CalendarSync.m_Sync && osync_member_objtype_enabled(member, "event") ) {
349 GetChanges(ctx, env, &env->m_CalendarSync,
350 "Calendar", "event", "vevent20",
351 &VEventConverter::GetRecordData);
354 if( env->m_ContactsSync.m_Sync && osync_member_objtype_enabled(member, "contact") ) {
355 GetChanges(ctx, env, &env->m_ContactsSync,
356 "Address Book", "contact", "vcard30",
357 &VCardConverter::GetRecordData);
360 // Success!
361 osync_context_report_success(ctx);
363 // don't let exceptions escape to the C modules
364 catch( std::exception &e ) {
365 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
369 static osync_bool commit_change(OSyncContext *ctx, OSyncChange *change)
371 Trace trace("commit_change");
373 // We can rely on a valid record state table, since get_changeinfo()
374 // will be called first, and will fill the table.
376 try {
378 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
380 // find the needed commit function, based on objtype of the change
381 CommitData_t CommitData = GetCommitFunction(change);
382 if( !CommitData ) {
383 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
384 "unable to get commit function pointer");
385 return false;
388 // find the matching cache, state table, and id map for this change
389 DatabaseSyncState *pSync = env->GetSyncObject(change);
390 if( !pSync ) {
391 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
392 "unable to get sync object that matches change type");
393 return false;
396 // is syncing turned on for this type?
397 if( !pSync->m_Sync ) {
398 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
399 "This object type is disabled in the barry-sync config");
400 return false;
403 // make references instead of pointers
404 DatabaseSyncState::cache_type &cache = pSync->m_Cache;
405 Barry::RecordStateTable &table = pSync->m_Table;
406 idmap &map = pSync->m_IdMap;
407 Barry::Mode::Desktop &desktop = *env->GetDesktop();
408 unsigned int dbId = pSync->m_dbId;
411 // extract RecordId from change's UID,
412 // and update the ID map if necessary
413 const char *uid = osync_change_get_uid(change);
414 trace.logf("uid from change: %s", uid);
415 if( strlen(uid) == 0 ) {
416 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
417 "uid from change object is blank!");
419 unsigned long RecordId = pSync->GetMappedRecordId(uid);
421 // search for the RecordId in the state table, to find the
422 // index... we only need the index if we are deleting or
423 // modifying
424 Barry::RecordStateTable::IndexType StateIndex;
425 if( osync_change_get_changetype(change) != CHANGE_ADDED ) {
426 if( !table.GetIndex(RecordId, &StateIndex) ) {
427 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
428 "unable to get state table index for RecordId: %lu",
429 RecordId);
430 return false;
434 std::string errmsg;
435 bool status;
437 switch( osync_change_get_changetype(change) )
439 case CHANGE_DELETED:
440 desktop.DeleteRecord(dbId, StateIndex);
441 cache.erase(RecordId);
442 map.UnmapUid(uid);
443 break;
445 case CHANGE_ADDED:
446 status = (*CommitData)(env, dbId, StateIndex, RecordId,
447 osync_change_get_data(change), true, errmsg);
448 if( !status ) {
449 trace.logf("CommitData() for ADDED state returned false: %s", errmsg.c_str());
450 osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str());
451 map.UnmapUid(uid);
452 return false;
454 cache[RecordId] = false;
455 break;
457 case CHANGE_MODIFIED:
458 status = (*CommitData)(env, dbId, StateIndex, RecordId,
459 osync_change_get_data(change), false, errmsg);
460 if( !status ) {
461 trace.logf("CommitData() for MODIFIED state returned false: %s", errmsg.c_str());
462 osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str());
463 map.UnmapUid(uid);
464 return false;
466 break;
468 default:
469 trace.log("Unknown change type");
470 osync_debug("barry-sync", 0, "Unknown change type");
471 break;
474 // Answer the call
475 osync_context_report_success(ctx);
476 return true;
480 catch( std::exception &e ) {
481 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
483 // we don't worry about unmapping ids here, as there
484 // is still a possibility that the record was added...
485 // plus, the map might not get written out to disk anyway
486 // in a plugin error state
488 return false;
492 static void sync_done(OSyncContext *ctx)
495 // This function will only be called if the sync was successfull
498 Trace trace("sync_done");
500 try {
502 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
504 // we reconnect to the device here, since dirty flags
505 // for records we've just touched do not show up until
506 // a disconnect... as far as I can tell.
507 env->Reconnect();
509 // do cleanup for each database
510 if( FinishSync(ctx, env, &env->m_CalendarSync) &&
511 FinishSync(ctx, env, &env->m_ContactsSync) )
513 // Success
514 osync_context_report_success(ctx);
518 catch( std::exception &e ) {
519 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
523 static void disconnect(OSyncContext *ctx)
525 Trace trace("disconnect");
527 // Disconnect the controller, which closes our connection
528 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
529 env->Disconnect();
531 // Done!
532 osync_context_report_success(ctx);
535 static void finalize(void *data)
537 Trace trace("finalize");
539 BarryEnvironment *env = (BarryEnvironment *)data;
540 delete env;
543 void get_info(OSyncEnv *env)
545 Trace trace("get_info");
547 // Create first plugin
548 OSyncPluginInfo *info = osync_plugin_new_info(env);
550 info->name = "barry-sync";
551 info->longname = "Barry OpenSync plugin v" PACKAGE_VERSION " for the Blackberry handheld";
552 info->description = "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
553 info->version = 1; // API version (opensync api?)
554 info->is_threadsafe = TRUE;
556 info->functions.initialize = initialize;
557 info->functions.connect = connect;
558 info->functions.sync_done = sync_done;
559 info->functions.disconnect = disconnect;
560 info->functions.finalize = finalize;
561 info->functions.get_changeinfo = get_changeinfo;
563 // If you like, you can overwrite the default timeouts of your plugin
564 // The default is set to 60 sec. Note that this MUST NOT be used to
565 // wait for expected timeouts (Lets say while waiting for a webserver).
566 // you should wait for the normal timeout and return a error.
567 // info->timeouts.connect_timeout = 5;
568 // There are more timeouts for the other functions
571 // Register each supported feature
574 // Calendar entries, using batch commit
575 osync_plugin_accept_objtype(info, "event");
576 osync_plugin_accept_objformat(info, "event", "vevent20", NULL);
577 osync_plugin_set_commit_objformat(info, "event", "vevent20",
578 commit_change);
580 // Address Book entries
581 osync_plugin_accept_objtype(info, "contact");
582 osync_plugin_accept_objformat(info, "contact", "vcard30", NULL);
583 osync_plugin_set_commit_objformat(info, "contact", "vcard30",
584 commit_change);