3 // Opensync module for the USB Blackberry handheld
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"
37 // All functions that are callable from outside must look like 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
,
58 const char *ObjTypeName
, const char *FormatName
,
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");
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
);
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
);
107 // in the cache... dirty?
110 trace
.log("found a MODIFIED change");
111 change
= osync_change_new();
112 osync_change_set_changetype(change
, CHANGE_MODIFIED
);
115 trace
.log("no change detected");
119 // finish filling out the change object
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
)
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());
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.
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
;
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
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
);
221 if( !pSync
->SaveCache() ) {
222 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
223 "Error saving calendar cache");
229 if( !pSync
->SaveMap() ) {
230 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
231 "Error saving calendar id map");
235 // clear all dirty flags in device
236 env
->ClearDirtyFlags(pSync
->m_Table
, pSync
->m_dbName
);
242 //////////////////////////////////////////////////////////////////////////////
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
255 env
= new BarryEnvironment(member
);
257 // Load config file for this plugin
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
));
267 // Process the configdata here and set the options on your environment
268 env
->ParseConfig(configdata
, configsize
);
271 // FIXME - near the end of release, do a run with
272 // this set to true, and look for USB protocol
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();
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());
296 catch( std::exception
&e
) {
297 osync_error_update(error
, "%s", e
.what());
303 static void connect(OSyncContext
*ctx
)
305 Trace
trace("connect");
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
316 int nIndex
= probe
.FindActive(env
->m_pin
);
318 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, "Unable to find PIN %lx", env
->m_pin
);
322 env
->Connect(probe
.Get(nIndex
));
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
,
339 static void get_changeinfo(OSyncContext
*ctx
)
341 Trace
trace("get_changeinfo");
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
);
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.
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
);
383 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
384 "unable to get commit function pointer");
388 // find the matching cache, state table, and id map for this change
389 DatabaseSyncState
*pSync
= env
->GetSyncObject(change
);
391 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
392 "unable to get sync object that matches change type");
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");
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
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",
437 switch( osync_change_get_changetype(change
) )
440 desktop
.DeleteRecord(dbId
, StateIndex
);
441 cache
.erase(RecordId
);
446 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
447 osync_change_get_data(change
), true, errmsg
);
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());
454 cache
[RecordId
] = false;
457 case CHANGE_MODIFIED
:
458 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
459 osync_change_get_data(change
), false, errmsg
);
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());
469 trace
.log("Unknown change type");
470 osync_debug("barry-sync", 0, "Unknown change type");
475 osync_context_report_success(ctx
);
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
492 static void sync_done(OSyncContext
*ctx
)
495 // This function will only be called if the sync was successfull
498 Trace
trace("sync_done");
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.
509 // do cleanup for each database
510 if( FinishSync(ctx
, env
, &env
->m_CalendarSync
) &&
511 FinishSync(ctx
, env
, &env
->m_ContactsSync
) )
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
);
532 osync_context_report_success(ctx
);
535 static void finalize(void *data
)
537 Trace
trace("finalize");
539 BarryEnvironment
*env
= (BarryEnvironment
*)data
;
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",
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",