3 // Opensync module for the USB Blackberry handheld
7 Copyright (C) 2006-2009, 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 <opensync/opensync-data.h>
24 #include <opensync/opensync-format.h>
25 #include <opensync/opensync-plugin.h>
26 #include <opensync/opensync-context.h>
27 #include <opensync/opensync-helper.h>
28 #include <opensync/opensync-version.h>
30 #include <barry/barry.h>
31 #include <barry/dll.h>
32 #include "barry_sync.h"
33 #include "environment.h"
42 // All functions that are callable from outside must look like C
44 static void *initialize(OSyncPlugin
*plugin
, OSyncPluginInfo
*info
, OSyncError
**error
);
45 static osync_bool
discover(void *userdata
, OSyncPluginInfo
*info
, OSyncError
**error
);
46 static void connect(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
);
47 static void get_changes(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
);
48 static void commit_change(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
, OSyncChange
*change
);
49 static void sync_done(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
);
50 static void disconnect(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
);
51 static void finalize(void *userdata
);
52 BXEXPORT osync_bool
get_sync_info(OSyncPluginEnv
*env
, OSyncError
**error
);
56 //////////////////////////////////////////////////////////////////////////////
58 // Support functions and classes
62 void GetChanges(OSyncPluginInfo
*info
, OSyncContext
*ctx
, BarryEnvironment
*env
,
63 DatabaseSyncState
*pSync
,
65 const char *ObjTypeName
, const char *FormatName
,
68 Trace
trace("GetChanges");
70 OSyncError
*error
= NULL
;
72 // shortcut references
73 using namespace Barry
;
74 using Barry::RecordStateTable
;
75 Mode::Desktop
&desktop
= *env
->m_pDesktop
;
77 // find the matching cache, state table, and id map for this change
78 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
79 idmap
&map
= pSync
->m_IdMap
;
81 // check if slow sync has been requested, and if so, empty the
82 // cache and id map and start fresh
83 if (osync_objtype_sink_get_slowsync(pSync
->sink
)) {
84 trace
.log("GetChanges: slow sync request detected, clearing cache and id map");
90 unsigned int dbId
= desktop
.GetDBID(DBDBName
);
91 RecordStateTable
&table
= pSync
->m_Table
;
92 desktop
.GetRecordStateTable(dbId
, table
);
94 OSyncFormatEnv
*formatenv
= osync_plugin_info_get_format_env(info
);
96 // OSyncObjTypeSink *sink = osync_plugin_info_get_sink(info);
99 // cycle through the state table...
100 // - if not in cache, it is added.
101 // - if in cache, check Blackberry's dirty flag
102 RecordStateTable::StateMapType::const_iterator i
= table
.StateMap
.begin();
103 for( ; i
!= table
.StateMap
.end(); ++i
) {
105 OSyncChange
*change
= 0;
106 const RecordStateTable::IndexType
&index
= i
->first
;
107 const RecordStateTable::State
&state
= i
->second
;
109 // search the idmap for the UID
110 std::string uid
= pSync
->Map2Uid(state
.RecordId
);
113 DatabaseSyncState::cache_type::const_iterator c
= cache
.find(state
.RecordId
);
114 if( c
== cache
.end() ) {
115 // not in cache, this is a new item
116 trace
.log("found an ADDED change");
117 change
= osync_change_new(&error
);
118 osync_change_set_changetype(change
, OSYNC_CHANGE_TYPE_ADDED
);
121 // in the cache... dirty?
124 trace
.log("found a MODIFIED change");
125 change
= osync_change_new(&error
);
126 osync_change_set_changetype(change
, OSYNC_CHANGE_TYPE_MODIFIED
);
129 trace
.log("no change detected");
133 // finish filling out the change object
135 // osync_change_set_member(change, env->member);
136 // osync_change_set_objformat_string(change, FormatName);
138 osync_change_set_uid(change
, uid
.c_str());
139 trace
.logf("change record ID: %s", uid
.c_str());
141 OSyncObjFormat
*format
= osync_format_env_find_objformat(formatenv
, FormatName
);
143 // Now you can set the data for the object
144 // Set the last argument to FALSE if the real data
145 // should be queried later in a "get_data" function
146 char *data
= (*getdata
)(env
, dbId
, index
);
147 OSyncData
*odata
= osync_data_new(data
, strlen(data
), format
, &error
);
149 // osync_data_set_objtype(odata, osync_objtype_sink_get_name(sink));
151 osync_change_set_data(change
, odata
);
152 osync_data_unref(odata
);
154 // just report the change via
155 osync_context_report_change(ctx
, change
);
157 osync_change_unref(change
);
159 // map our IDs for later
160 map
.Map(uid
, state
.RecordId
);
164 // now cycle through the cache... any objects in the cache
165 // but not found in the state table means that they have been
166 // deleted in the device
167 DatabaseSyncState::cache_type::const_iterator c
= cache
.begin();
168 for( ; c
!= cache
.end(); ++c
) {
169 uint32_t recordId
= c
->first
;
171 // search the idmap for the UID
172 std::string uid
= pSync
->Map2Uid(recordId
);
174 // search the state table
175 i
= table
.StateMap
.begin();
176 for( ; i
!= table
.StateMap
.end(); ++i
) {
178 if( i
->second
.RecordId
== recordId
)
182 // check if not found...
183 if( i
== table
.StateMap
.end() ) {
184 // register a DELETE, no data
185 trace
.log("found DELETE change");
187 OSyncChange
*change
= osync_change_new(&error
);
189 osync_change_set_changetype(change
, OSYNC_CHANGE_TYPE_DELETED
);
191 // osync_change_set_member(change, env->member);
192 // osync_change_set_objformat_string(change, FormatName);
194 osync_change_set_uid(change
, uid
.c_str());
195 trace
.log(uid
.c_str());
197 OSyncObjFormat
*format
= osync_format_env_find_objformat(formatenv
, FormatName
);
199 OSyncData
*odata
= osync_data_new(NULL
, 0, format
, &error
);
201 // osync_data_set_objtype(odata, osync_objtype_sink_get_name(sink));
203 osync_change_set_data(change
, odata
);
204 osync_data_unref(odata
);
207 osync_context_report_change(ctx
, change
);
209 osync_change_unref(change
);
213 // finally, cycle through the state map again, and overwrite the
214 // cache with the current state table. Memory only... if successful,
215 // it will be written back to disk later on.
220 for( i
= table
.StateMap
.begin(); i
!= table
.StateMap
.end(); ++i
) {
221 const RecordStateTable::State
&state
= i
->second
;
222 cache
[state
.RecordId
] = false;
226 CommitData_t
GetCommitFunction(OSyncChange
*change
)
228 const char *name
= osync_change_get_objtype(change
);
230 if( strcmp(name
, "event") == 0 ) {
231 return &VEventConverter::CommitRecordData
;
233 else if( strcmp(name
, "contact") == 0 ) {
234 return &VCardConverter::CommitRecordData
;
241 bool FinishSync(OSyncContext
*ctx
, BarryEnvironment
*env
, DatabaseSyncState
*pSync
)
243 Trace
trace("FinishSync()");
245 if( !pSync
->m_Sync
) {
246 // this mode is disabled in config, skip
250 Barry::Mode::Desktop
&desktop
= *env
->m_pDesktop
;
252 // get the state table again, so we can update
253 // the cache properly
254 desktop
.GetRecordStateTable(pSync
->m_dbId
, pSync
->m_Table
);
257 if( !pSync
->SaveCache() ) {
258 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
259 "Error saving calendar cache");
265 if( !pSync
->SaveMap() ) {
266 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
267 "Error saving calendar id map");
271 // clear all dirty flags in device
272 env
->ClearDirtyFlags(pSync
->m_Table
, pSync
->m_dbName
);
277 static bool barry_contact_initialize(DatabaseSyncState
*env
, OSyncPluginInfo
*info
, OSyncError
**error
)
279 OSyncObjTypeSink
*sink
= osync_plugin_info_find_objtype(info
, "contact");
282 osync_bool sinkEnabled
= osync_objtype_sink_is_enabled(sink
);
286 OSyncObjTypeSinkFunctions functions
;
287 memset(&functions
, 0, sizeof(functions
));
289 functions
.connect
= connect
;
290 functions
.disconnect
= disconnect
;
291 functions
.get_changes
= get_changes
;
292 functions
.commit
= commit_change
;
293 functions
.sync_done
= sync_done
;
295 OSyncPluginConfig
*config
= osync_plugin_info_get_config(info
);
296 OSyncPluginResource
*resource
= osync_plugin_config_find_active_resource(config
, "contact");
298 OSyncList
*objformatsinks
= osync_plugin_resource_get_objformat_sinks(resource
);
300 bool hasObjFormat
= false;
303 for(r
= objformatsinks
;r
;r
= r
->next
) {
304 OSyncObjFormatSink
*objformatsink
= (OSyncObjFormatSink
*) r
->data
;
306 if(!strcmp("vcard30", osync_objformat_sink_get_objformat(objformatsink
))) {
316 // OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info);
318 // env->format = osync_format_env_find_objformat(formatenv, "vcard30");
321 osync_objtype_sink_set_functions(sink
, functions
, NULL
);
327 static bool barry_calendar_initialize(DatabaseSyncState
*env
, OSyncPluginInfo
*info
, OSyncError
**error
)
329 OSyncObjTypeSink
*sink
= osync_plugin_info_find_objtype(info
, "event");
332 osync_bool sinkEnabled
= osync_objtype_sink_is_enabled(sink
);
336 OSyncObjTypeSinkFunctions functions
;
337 memset(&functions
, 0, sizeof(functions
));
339 functions
.connect
= connect
;
340 functions
.disconnect
= disconnect
;
341 functions
.get_changes
= get_changes
;
342 functions
.commit
= commit_change
;
343 functions
.sync_done
= sync_done
;
345 OSyncPluginConfig
*config
= osync_plugin_info_get_config(info
);
346 OSyncPluginResource
*resource
= osync_plugin_config_find_active_resource(config
, "event");
348 OSyncList
*objformatsinks
= osync_plugin_resource_get_objformat_sinks(resource
);
350 bool hasObjFormat
= false;
353 for(r
= objformatsinks
;r
;r
= r
->next
) {
354 OSyncObjFormatSink
*objformatsink
= (OSyncObjFormatSink
*) r
->data
;
356 if(!strcmp("vevent20", osync_objformat_sink_get_objformat(objformatsink
))) {
366 // OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info);
368 // env->format = osync_format_env_find_objformat(formatenv, "vevent20");
371 osync_objtype_sink_set_functions(sink
, functions
, NULL
);
379 //////////////////////////////////////////////////////////////////////////////
384 static void *initialize(OSyncPlugin
*plugin
, OSyncPluginInfo
*info
, OSyncError
**error
)
386 Trace
trace("initialize");
388 BarryEnvironment
*env
= 0;
391 env
= new BarryEnvironment(info
);
396 OSyncPluginConfig
*config
= osync_plugin_info_get_config(info
);
399 osync_error_set(error
, OSYNC_ERROR_GENERIC
, "Unable to get config.");
407 * Process plugin specific advanced options
409 OSyncList
*optslist
= osync_plugin_config_get_advancedoptions(config
);
410 for (; optslist
; optslist
= optslist
->next
) {
411 OSyncPluginAdvancedOption
*option
= (OSyncPluginAdvancedOption
*) optslist
->data
;
413 const char *val
= osync_plugin_advancedoption_get_value(option
);
414 const char *name
= osync_plugin_advancedoption_get_name(option
);
416 if (!strcmp(name
, "PinCode")) {
417 env
->m_pin
= atoi(val
);
419 else if (!strcmp(name
, "Debug")) {
420 env
->m_DebugMode
= (!strcmp(val
, "1")) ? true : false;
424 OSyncPluginAuthentication
*optauth
= osync_plugin_config_get_authentication(config
);
426 if (osync_plugin_authentication_option_is_supported(optauth
, OSYNC_PLUGIN_AUTHENTICATION_PASSWORD
)) {
427 const char *val
= osync_plugin_authentication_get_password(optauth
);
429 env
->m_password
= val
;
433 // FIXME - near the end of release, do a run with
434 // this set to true, and look for USB protocol
436 Barry::Init(env
->m_DebugMode
);
440 * Process Ressource options
442 if (!barry_calendar_initialize(&env
->m_CalendarSync
, info
, error
)) {
443 env
->m_CalendarSync
.LoadCache();
444 env
->m_CalendarSync
.LoadMap();
447 env
->m_CalendarSync
.m_Sync
= false;
449 if (!barry_contact_initialize(&env
->m_ContactsSync
, info
, error
)) {
450 env
->m_ContactsSync
.LoadCache();
451 env
->m_ContactsSync
.LoadMap();
454 env
->m_ContactsSync
.m_Sync
= false;
459 catch( std::exception
&e
) {
467 static osync_bool
discover(void *userdata
, OSyncPluginInfo
*info
, OSyncError
**error
)
469 int i
, numobjs
= osync_plugin_info_num_objtypes(info
);
471 for (i
= 0; i
< numobjs
; i
++) {
472 OSyncObjTypeSink
*sink
= osync_plugin_info_nth_objtype(info
, i
);
474 osync_objtype_sink_set_available(sink
, true);
477 OSyncVersion
*version
= osync_version_new(error
);
478 osync_version_set_plugin(version
, "Barry");
479 osync_version_set_modelversion(version
, "1");
480 //osync_version_set_firmwareversion(version, "firmwareversion");
481 //osync_version_set_softwareversion(version, "softwareversion");
482 //osync_version_set_hardwareversion(version, "hardwareversion");
483 osync_plugin_info_set_version(info
, version
);
484 osync_version_unref(version
);
490 static void connect(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
492 Trace
trace("connect");
496 // Each time you get passed a context (which is used to track
497 // calls to your plugin) you can get the data your returned in
498 // initialize via this call:
499 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
501 // Probe for available devices
503 int nIndex
= probe
.FindActive(env
->m_pin
);
505 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, "Unable to find PIN %lx", env
->m_pin
);
508 env
->m_ProbeResult
= probe
.Get(nIndex
);
510 env
->Connect(probe
.Get(nIndex
));
513 osync_context_report_success(ctx
);
516 // Don't let exceptions escape to the C modules
517 catch( std::bad_alloc
&ba
) {
518 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
519 "Unable to allocate memory for controller: %s", ba
.what());
521 catch( std::exception
&e
) {
522 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
528 static void get_changes(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
530 Trace
trace("get_changeinfo");
534 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
536 if( env
->m_CalendarSync
.m_Sync
) {
537 GetChanges(info
, ctx
, env
, &env
->m_CalendarSync
,
538 "Calendar", "event", "vevent20",
539 &VEventConverter::GetRecordData
);
542 if( env
->m_ContactsSync
.m_Sync
) {
543 GetChanges(info
, ctx
, env
, &env
->m_ContactsSync
,
544 "Address Book", "contact", "vcard30",
545 &VCardConverter::GetRecordData
);
549 osync_context_report_success(ctx
);
551 // don't let exceptions escape to the C modules
552 catch( std::exception
&e
) {
553 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
557 static void commit_change(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
, OSyncChange
*change
)
559 Trace
trace("commit_change");
561 // We can rely on a valid record state table, since get_changeinfo()
562 // will be called first, and will fill the table.
566 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
568 // find the needed commit function, based on objtype of the change
569 CommitData_t CommitData
= GetCommitFunction(change
);
571 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
572 "unable to get commit function pointer");
576 // find the matching cache, state table, and id map for this change
577 DatabaseSyncState
*pSync
= env
->GetSyncObject(change
);
579 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
580 "unable to get sync object that matches change type");
584 // is syncing turned on for this type?
585 if( !pSync
->m_Sync
) {
586 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
587 "This object type is disabled in the barry-sync config");
591 // make references instead of pointers
592 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
593 Barry::RecordStateTable
&table
= pSync
->m_Table
;
594 idmap
&map
= pSync
->m_IdMap
;
595 Barry::Mode::Desktop
&desktop
= *env
->m_pDesktop
;
596 unsigned int dbId
= pSync
->m_dbId
;
599 // extract RecordId from change's UID,
600 // and update the ID map if necessary
601 const char *uid
= osync_change_get_uid(change
);
602 trace
.logf("uid from change: %s", uid
);
603 if( strlen(uid
) == 0 ) {
604 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
605 "uid from change object is blank!");
607 unsigned long RecordId
= pSync
->GetMappedRecordId(uid
);
609 // search for the RecordId in the state table, to find the
610 // index... we only need the index if we are deleting or
612 Barry::RecordStateTable::IndexType StateIndex
;
613 if( osync_change_get_changetype(change
) != OSYNC_CHANGE_TYPE_ADDED
) {
614 if( !table
.GetIndex(RecordId
, &StateIndex
) ) {
615 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
616 "unable to get state table index for RecordId: %lu",
625 OSyncData
*odata
= NULL
;
628 switch( osync_change_get_changetype(change
) )
630 case OSYNC_CHANGE_TYPE_DELETED
:
631 desktop
.DeleteRecord(dbId
, StateIndex
);
632 cache
.erase(RecordId
);
636 case OSYNC_CHANGE_TYPE_ADDED
:
637 odata
= osync_change_get_data(change
);
638 osync_data_get_data(odata
, &plain
, NULL
);
639 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
640 plain
, true, errmsg
);
642 trace
.logf("CommitData() for ADDED state returned false: %s", errmsg
.c_str());
643 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
647 cache
[RecordId
] = false;
650 case OSYNC_CHANGE_TYPE_MODIFIED
:
651 odata
= osync_change_get_data(change
);
652 osync_data_get_data(odata
, &plain
, NULL
);
653 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
654 plain
, false, errmsg
);
656 trace
.logf("CommitData() for MODIFIED state returned false: %s", errmsg
.c_str());
657 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
664 trace
.log("Unknown change type");
669 osync_context_report_success(ctx
);
674 catch( std::exception
&e
) {
675 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
677 // we don't worry about unmapping ids here, as there
678 // is still a possibility that the record was added...
679 // plus, the map might not get written out to disk anyway
680 // in a plugin error state
686 static void sync_done(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
689 // This function will only be called if the sync was successfull
692 Trace
trace("sync_done");
696 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
698 // we reconnect to the device here, since dirty flags
699 // for records we've just touched do not show up until
700 // a disconnect... as far as I can tell.
703 // do cleanup for each database
704 if( FinishSync(ctx
, env
, &env
->m_CalendarSync
) &&
705 FinishSync(ctx
, env
, &env
->m_ContactsSync
) )
708 osync_context_report_success(ctx
);
712 catch( std::exception
&e
) {
713 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
717 static void disconnect(void *userdata
, OSyncPluginInfo
*info
, OSyncContext
*ctx
)
719 Trace
trace("disconnect");
721 // Disconnect the controller, which closes our connection
722 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
726 osync_context_report_success(ctx
);
729 static void finalize(void *userdata
)
731 Trace
trace("finalize");
733 BarryEnvironment
*env
= (BarryEnvironment
*) userdata
;
739 osync_bool
get_sync_info(OSyncPluginEnv
*env
, OSyncError
**error
)
741 // Create a new OpenSync plugin
742 OSyncPlugin
*plugin
= osync_plugin_new(error
);
745 osync_error_unref(error
);
750 // Describe our plugin
751 osync_plugin_set_name(plugin
, "barry-sync");
752 osync_plugin_set_longname(plugin
, "Barry OpenSync plugin v0.15 for the Blackberry handheld");
753 osync_plugin_set_description(plugin
, "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds");
755 // Set the callback functions
756 osync_plugin_set_initialize(plugin
, initialize
);
757 osync_plugin_set_finalize(plugin
, finalize
);
758 osync_plugin_set_discover(plugin
, discover
);
760 osync_plugin_env_register_plugin(env
, plugin
);
761 osync_plugin_unref(plugin
);
767 int get_version(void)