3 // Opensync module for the USB Blackberry handheld
7 Copyright (C) 2006-2010, 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"
35 // All functions that are callable from outside must look like C
37 static void *initialize(OSyncMember
*member
, OSyncError
**error
);
38 static void connect(OSyncContext
*ctx
);
39 static void get_changeinfo(OSyncContext
*ctx
);
40 static void sync_done(OSyncContext
*ctx
);
41 static void disconnect(OSyncContext
*ctx
);
42 static void finalize(void *data
);
43 BXEXPORT
void get_info(OSyncEnv
*env
);
47 //////////////////////////////////////////////////////////////////////////////
49 // Support functions and classes
53 void GetChanges(OSyncContext
*ctx
, BarryEnvironment
*env
,
54 DatabaseSyncState
*pSync
,
56 const char *ObjTypeName
, const char *FormatName
,
59 Trace
trace("GetChanges");
61 // shortcut references
62 using namespace Barry
;
63 using Barry::RecordStateTable
;
64 Mode::Desktop
&desktop
= *env
->m_pDesktop
;
66 // find the matching cache, state table, and id map for this change
67 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
68 idmap
&map
= pSync
->m_IdMap
;
70 // check if slow sync has been requested, and if so, empty the
71 // cache and id map and start fresh
72 if( osync_member_get_slow_sync(env
->member
, ObjTypeName
) ) {
73 trace
.log("GetChanges: slow sync request detected, clearing cache and id map");
79 unsigned int dbId
= desktop
.GetDBID(DBDBName
);
80 RecordStateTable
&table
= pSync
->m_Table
;
81 desktop
.GetRecordStateTable(dbId
, table
);
83 // cycle through the state table...
84 // - if not in cache, it is added.
85 // - if in cache, check Blackberry's dirty flag
86 RecordStateTable::StateMapType::const_iterator i
= table
.StateMap
.begin();
87 for( ; i
!= table
.StateMap
.end(); ++i
) {
89 OSyncChange
*change
= 0;
90 const RecordStateTable::IndexType
&index
= i
->first
;
91 const RecordStateTable::State
&state
= i
->second
;
93 // search the idmap for the UID
94 std::string uid
= pSync
->Map2Uid(state
.RecordId
);
97 DatabaseSyncState::cache_type::const_iterator c
= cache
.find(state
.RecordId
);
98 if( c
== cache
.end() ) {
99 // not in cache, this is a new item
100 trace
.log("found an ADDED change");
101 change
= osync_change_new();
102 osync_change_set_changetype(change
, CHANGE_ADDED
);
105 // in the cache... dirty?
108 trace
.log("found a MODIFIED change");
109 change
= osync_change_new();
110 osync_change_set_changetype(change
, CHANGE_MODIFIED
);
113 trace
.log("no change detected");
117 // finish filling out the change object
119 osync_change_set_member(change
, env
->member
);
120 osync_change_set_objformat_string(change
, FormatName
);
122 osync_change_set_uid(change
, uid
.c_str());
123 trace
.logf("change record ID: %s", uid
.c_str());
125 // Now you can set the data for the object
126 // Set the last argument to FALSE if the real data
127 // should be queried later in a "get_data" function
128 char *data
= (*getdata
)(env
, dbId
, index
);
129 osync_change_set_data(change
, data
, strlen(data
), TRUE
);
131 // just report the change via
132 osync_context_report_change(ctx
, change
);
134 // map our IDs for later
135 map
.Map(uid
, state
.RecordId
);
139 // now cycle through the cache... any objects in the cache
140 // but not found in the state table means that they have been
141 // deleted in the device
142 DatabaseSyncState::cache_type::const_iterator c
= cache
.begin();
143 for( ; c
!= cache
.end(); ++c
) {
144 uint32_t recordId
= c
->first
;
146 // search the idmap for the UID
147 std::string uid
= pSync
->Map2Uid(recordId
);
149 // search the state table
150 i
= table
.StateMap
.begin();
151 for( ; i
!= table
.StateMap
.end(); ++i
) {
153 if( i
->second
.RecordId
== recordId
)
157 // check if not found...
158 if( i
== table
.StateMap
.end() ) {
159 // register a DELETE, no data
160 trace
.log("found DELETE change");
162 OSyncChange
*change
= osync_change_new();
163 osync_change_set_changetype(change
, CHANGE_DELETED
);
164 osync_change_set_member(change
, env
->member
);
165 osync_change_set_objformat_string(change
, FormatName
);
167 osync_change_set_uid(change
, uid
.c_str());
168 trace
.log(uid
.c_str());
171 osync_context_report_change(ctx
, change
);
175 // finally, cycle through the state map again, and overwrite the
176 // cache with the current state table. Memory only... if successful,
177 // it will be written back to disk later on.
182 for( i
= table
.StateMap
.begin(); i
!= table
.StateMap
.end(); ++i
) {
183 const RecordStateTable::State
&state
= i
->second
;
184 cache
[state
.RecordId
] = false;
188 CommitData_t
GetCommitFunction(OSyncChange
*change
)
190 OSyncObjType
*type
= osync_change_get_objtype(change
);
191 const char *name
= osync_objtype_get_name(type
);
192 if( strcmp(name
, "event") == 0 ) {
193 return &VEventConverter::CommitRecordData
;
195 else if( strcmp(name
, "contact") == 0 ) {
196 return &VCardConverter::CommitRecordData
;
203 bool FinishSync(OSyncContext
*ctx
, BarryEnvironment
*env
, DatabaseSyncState
*pSync
)
205 Trace
trace("FinishSync()");
207 if( !pSync
->m_Sync
) {
208 // this mode is disabled in config, skip
212 Barry::Mode::Desktop
&desktop
= *env
->m_pDesktop
;
214 // get the state table again, so we can update
215 // the cache properly
216 desktop
.GetRecordStateTable(pSync
->m_dbId
, pSync
->m_Table
);
219 if( !pSync
->SaveCache() ) {
220 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
221 "Error saving calendar cache");
227 if( !pSync
->SaveMap() ) {
228 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
229 "Error saving calendar id map");
233 // clear all dirty flags in device
234 env
->ClearDirtyFlags(pSync
->m_Table
, pSync
->m_dbName
);
240 //////////////////////////////////////////////////////////////////////////////
245 static void *initialize(OSyncMember
*member
, OSyncError
**error
)
247 Trace
trace("initialize");
249 BarryEnvironment
*env
= 0;
251 // Create the environment struct, including our Barry objects
253 env
= new BarryEnvironment(member
);
255 // Load config file for this plugin
258 if (!osync_member_get_config(member
, &configdata
, &configsize
, error
)) {
259 osync_error_update(error
, "Unable to get config data: %s",
260 osync_error_print(error
));
265 // Process the configdata here and set the options on your environment
266 env
->ParseConfig(configdata
, configsize
);
269 // FIXME - near the end of release, do a run with
270 // this set to true, and look for USB protocol
272 Barry::Init(env
->m_DebugMode
);
274 // Load all needed cache files
275 if( env
->m_CalendarSync
.m_Sync
) {
276 env
->m_CalendarSync
.LoadCache();
277 env
->m_CalendarSync
.LoadMap();
280 if( env
->m_ContactsSync
.m_Sync
) {
281 env
->m_ContactsSync
.LoadCache();
282 env
->m_ContactsSync
.LoadMap();
288 // Don't let C++ exceptions escape to the C code
289 catch( std::bad_alloc
&ba
) {
290 osync_error_update(error
, "Unable to allocate memory for environment: %s", ba
.what());
294 catch( std::exception
&e
) {
295 osync_error_update(error
, "%s", e
.what());
301 static void connect(OSyncContext
*ctx
)
303 Trace
trace("connect");
307 // Each time you get passed a context (which is used to track
308 // calls to your plugin) you can get the data your returned in
309 // initialize via this call:
310 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
312 // Probe for available devices
314 int nIndex
= probe
.FindActive(env
->m_pin
);
316 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, "Unable to find PIN %lx", env
->m_pin
);
319 env
->m_ProbeResult
.reset( new Barry::ProbeResult(probe
.Get(nIndex
)) );
321 env
->Connect(probe
.Get(nIndex
));
324 osync_context_report_success(ctx
);
327 // Don't let exceptions escape to the C modules
328 catch( std::bad_alloc
&ba
) {
329 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
330 "Unable to allocate memory for controller: %s", ba
.what());
332 catch( std::exception
&e
) {
333 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
338 static void get_changeinfo(OSyncContext
*ctx
)
340 Trace
trace("get_changeinfo");
344 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
346 if( env
->m_CalendarSync
.m_Sync
) {
347 GetChanges(ctx
, env
, &env
->m_CalendarSync
,
348 "Calendar", "event", "vevent20",
349 &VEventConverter::GetRecordData
);
352 if( env
->m_ContactsSync
.m_Sync
) {
353 GetChanges(ctx
, env
, &env
->m_ContactsSync
,
354 "Address Book", "contact", "vcard30",
355 &VCardConverter::GetRecordData
);
359 osync_context_report_success(ctx
);
361 // don't let exceptions escape to the C modules
362 catch( std::exception
&e
) {
363 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
367 static osync_bool
commit_change(OSyncContext
*ctx
, OSyncChange
*change
)
369 Trace
trace("commit_change");
371 // We can rely on a valid record state table, since get_changeinfo()
372 // will be called first, and will fill the table.
376 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
378 // find the needed commit function, based on objtype of the change
379 CommitData_t CommitData
= GetCommitFunction(change
);
381 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
382 "unable to get commit function pointer");
386 // find the matching cache, state table, and id map for this change
387 DatabaseSyncState
*pSync
= env
->GetSyncObject(change
);
389 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
390 "unable to get sync object that matches change type");
394 // is syncing turned on for this type?
395 if( !pSync
->m_Sync
) {
396 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
397 "This object type is disabled in the barry-sync config");
401 // make references instead of pointers
402 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
403 Barry::RecordStateTable
&table
= pSync
->m_Table
;
404 idmap
&map
= pSync
->m_IdMap
;
405 Barry::Mode::Desktop
&desktop
= *env
->m_pDesktop
;
406 unsigned int dbId
= pSync
->m_dbId
;
409 // extract RecordId from change's UID,
410 // and update the ID map if necessary
411 const char *uid
= osync_change_get_uid(change
);
412 trace
.logf("uid from change: %s", uid
);
413 if( strlen(uid
) == 0 ) {
414 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
415 "uid from change object is blank!");
417 unsigned long RecordId
= pSync
->GetMappedRecordId(uid
);
419 // search for the RecordId in the state table, to find the
420 // index... we only need the index if we are deleting or
422 Barry::RecordStateTable::IndexType StateIndex
;
423 if( osync_change_get_changetype(change
) != CHANGE_ADDED
) {
424 if( !table
.GetIndex(RecordId
, &StateIndex
) ) {
425 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
426 "unable to get state table index for RecordId: %lu",
435 switch( osync_change_get_changetype(change
) )
438 desktop
.DeleteRecord(dbId
, StateIndex
);
439 cache
.erase(RecordId
);
444 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
445 osync_change_get_data(change
), true, errmsg
);
447 trace
.logf("CommitData() for ADDED state returned false: %s", errmsg
.c_str());
448 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
452 cache
[RecordId
] = false;
455 case CHANGE_MODIFIED
:
456 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
457 osync_change_get_data(change
), false, errmsg
);
459 trace
.logf("CommitData() for MODIFIED state returned false: %s", errmsg
.c_str());
460 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
467 trace
.log("Unknown change type");
468 osync_debug("barry-sync", 0, "Unknown change type");
473 osync_context_report_success(ctx
);
478 catch( std::exception
&e
) {
479 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
481 // we don't worry about unmapping ids here, as there
482 // is still a possibility that the record was added...
483 // plus, the map might not get written out to disk anyway
484 // in a plugin error state
490 static void sync_done(OSyncContext
*ctx
)
493 // This function will only be called if the sync was successfull
496 Trace
trace("sync_done");
500 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
502 // we reconnect to the device here, since dirty flags
503 // for records we've just touched do not show up until
504 // a disconnect... as far as I can tell.
507 // do cleanup for each database
508 if( FinishSync(ctx
, env
, &env
->m_CalendarSync
) &&
509 FinishSync(ctx
, env
, &env
->m_ContactsSync
) )
512 osync_context_report_success(ctx
);
516 catch( std::exception
&e
) {
517 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
521 static void disconnect(OSyncContext
*ctx
)
523 Trace
trace("disconnect");
525 // Disconnect the controller, which closes our connection
526 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
530 osync_context_report_success(ctx
);
533 static void finalize(void *data
)
535 Trace
trace("finalize");
537 BarryEnvironment
*env
= (BarryEnvironment
*)data
;
541 void get_info(OSyncEnv
*env
)
543 Trace
trace("get_info");
545 // Create first plugin
546 OSyncPluginInfo
*info
= osync_plugin_new_info(env
);
548 info
->name
= "barry-sync";
549 info
->longname
= "Barry OpenSync plugin v0.17 for the Blackberry handheld";
550 info
->description
= "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
551 info
->version
= 1; // API version (opensync api?)
552 info
->is_threadsafe
= TRUE
;
554 info
->functions
.initialize
= initialize
;
555 info
->functions
.connect
= connect
;
556 info
->functions
.sync_done
= sync_done
;
557 info
->functions
.disconnect
= disconnect
;
558 info
->functions
.finalize
= finalize
;
559 info
->functions
.get_changeinfo
= get_changeinfo
;
561 // If you like, you can overwrite the default timeouts of your plugin
562 // The default is set to 60 sec. Note that this MUST NOT be used to
563 // wait for expected timeouts (Lets say while waiting for a webserver).
564 // you should wait for the normal timeout and return a error.
565 // info->timeouts.connect_timeout = 5;
566 // There are more timeouts for the other functions
569 // Register each supported feature
572 // Calendar entries, using batch commit
573 osync_plugin_accept_objtype(info
, "event");
574 osync_plugin_accept_objformat(info
, "event", "vevent20", NULL
);
575 osync_plugin_set_commit_objformat(info
, "event", "vevent20",
578 // Address Book entries
579 osync_plugin_accept_objtype(info
, "contact");
580 osync_plugin_accept_objformat(info
, "contact", "vcard30", NULL
);
581 osync_plugin_set_commit_objformat(info
, "contact", "vcard30",