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"
36 // All functions that are callable from outside must look like C
38 static void *initialize(OSyncMember
*member
, OSyncError
**error
);
39 static void connect(OSyncContext
*ctx
);
40 static void get_changeinfo(OSyncContext
*ctx
);
41 static void sync_done(OSyncContext
*ctx
);
42 static void disconnect(OSyncContext
*ctx
);
43 static void finalize(void *data
);
44 BXEXPORT
void get_info(OSyncEnv
*env
);
48 //////////////////////////////////////////////////////////////////////////////
50 // Support functions and classes
54 void GetChanges(OSyncContext
*ctx
, BarryEnvironment
*env
,
55 DatabaseSyncState
*pSync
,
57 const char *ObjTypeName
, const char *FormatName
,
60 Trace
trace("GetChanges");
62 // shortcut references
63 using namespace Barry
;
64 using Barry::RecordStateTable
;
65 Mode::Desktop
&desktop
= *env
->GetDesktop();
67 // find the matching cache, state table, and id map for this change
68 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
69 idmap
&map
= pSync
->m_IdMap
;
71 // check if slow sync has been requested, and if so, empty the
72 // cache and id map and start fresh
73 if( osync_member_get_slow_sync(env
->member
, ObjTypeName
) ) {
74 trace
.log("GetChanges: slow sync request detected, clearing cache and id map");
80 unsigned int dbId
= desktop
.GetDBID(DBDBName
);
81 RecordStateTable
&table
= pSync
->m_Table
;
82 desktop
.GetRecordStateTable(dbId
, table
);
84 // cycle through the state table...
85 // - if not in cache, it is added.
86 // - if in cache, check Blackberry's dirty flag
87 RecordStateTable::StateMapType::const_iterator i
= table
.StateMap
.begin();
88 for( ; i
!= table
.StateMap
.end(); ++i
) {
90 OSyncChange
*change
= 0;
91 const RecordStateTable::IndexType
&index
= i
->first
;
92 const RecordStateTable::State
&state
= i
->second
;
94 // search the idmap for the UID
95 std::string uid
= pSync
->Map2Uid(state
.RecordId
);
98 DatabaseSyncState::cache_type::const_iterator c
= cache
.find(state
.RecordId
);
99 if( c
== cache
.end() ) {
100 // not in cache, this is a new item
101 trace
.log("found an ADDED change");
102 change
= osync_change_new();
103 osync_change_set_changetype(change
, CHANGE_ADDED
);
106 // in the cache... dirty?
109 trace
.log("found a MODIFIED change");
110 change
= osync_change_new();
111 osync_change_set_changetype(change
, CHANGE_MODIFIED
);
114 trace
.log("no change detected");
118 // finish filling out the change object
120 osync_change_set_member(change
, env
->member
);
121 osync_change_set_objformat_string(change
, FormatName
);
123 osync_change_set_uid(change
, uid
.c_str());
124 trace
.logf("change record ID: %s", uid
.c_str());
126 // Now you can set the data for the object
127 // Set the last argument to FALSE if the real data
128 // should be queried later in a "get_data" function
129 char *data
= (*getdata
)(env
, dbId
, index
);
130 osync_change_set_data(change
, data
, strlen(data
), TRUE
);
132 // just report the change via
133 osync_context_report_change(ctx
, change
);
135 // map our IDs for later
136 map
.Map(uid
, state
.RecordId
);
140 // now cycle through the cache... any objects in the cache
141 // but not found in the state table means that they have been
142 // deleted in the device
143 DatabaseSyncState::cache_type::const_iterator c
= cache
.begin();
144 for( ; c
!= cache
.end(); ++c
) {
145 uint32_t recordId
= c
->first
;
147 // search the idmap for the UID
148 std::string uid
= pSync
->Map2Uid(recordId
);
150 // search the state table
151 i
= table
.StateMap
.begin();
152 for( ; i
!= table
.StateMap
.end(); ++i
) {
154 if( i
->second
.RecordId
== recordId
)
158 // check if not found...
159 if( i
== table
.StateMap
.end() ) {
160 // register a DELETE, no data
161 trace
.log("found DELETE change");
163 OSyncChange
*change
= osync_change_new();
164 osync_change_set_changetype(change
, CHANGE_DELETED
);
165 osync_change_set_member(change
, env
->member
);
166 osync_change_set_objformat_string(change
, FormatName
);
168 osync_change_set_uid(change
, uid
.c_str());
169 trace
.log(uid
.c_str());
172 osync_context_report_change(ctx
, change
);
176 // finally, cycle through the state map again, and overwrite the
177 // cache with the current state table. Memory only... if successful,
178 // it will be written back to disk later on.
183 for( i
= table
.StateMap
.begin(); i
!= table
.StateMap
.end(); ++i
) {
184 const RecordStateTable::State
&state
= i
->second
;
185 cache
[state
.RecordId
] = false;
189 CommitData_t
GetCommitFunction(OSyncChange
*change
)
191 OSyncObjType
*type
= osync_change_get_objtype(change
);
192 const char *name
= osync_objtype_get_name(type
);
193 if( strcmp(name
, "event") == 0 ) {
194 return &VEventConverter::CommitRecordData
;
196 else if( strcmp(name
, "contact") == 0 ) {
197 return &VCardConverter::CommitRecordData
;
204 bool FinishSync(OSyncContext
*ctx
, BarryEnvironment
*env
, DatabaseSyncState
*pSync
)
206 Trace
trace("FinishSync()");
208 if( !pSync
->m_Sync
) {
209 // this mode is disabled in config, skip
213 Barry::Mode::Desktop
&desktop
= *env
->GetDesktop();
215 // get the state table again, so we can update
216 // the cache properly
217 desktop
.GetRecordStateTable(pSync
->m_dbId
, pSync
->m_Table
);
220 if( !pSync
->SaveCache() ) {
221 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
222 "Error saving calendar cache");
228 if( !pSync
->SaveMap() ) {
229 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
230 "Error saving calendar id map");
234 // clear all dirty flags in device
235 env
->ClearDirtyFlags(pSync
->m_Table
, pSync
->m_dbName
);
241 //////////////////////////////////////////////////////////////////////////////
246 static void *initialize(OSyncMember
*member
, OSyncError
**error
)
248 Trace
trace("initialize");
250 BarryEnvironment
*env
= 0;
252 // Create the environment struct, including our Barry objects
254 env
= new BarryEnvironment(member
);
256 // Load config file for this plugin
259 if (!osync_member_get_config(member
, &configdata
, &configsize
, error
)) {
260 osync_error_update(error
, "Unable to get config data: %s",
261 osync_error_print(error
));
266 // Process the configdata here and set the options on your environment
267 env
->ParseConfig(configdata
, configsize
);
270 // FIXME - near the end of release, do a run with
271 // this set to true, and look for USB protocol
273 Barry::Init(env
->m_DebugMode
);
275 // Load all needed cache files
276 if( env
->m_CalendarSync
.m_Sync
) {
277 env
->m_CalendarSync
.LoadCache();
278 env
->m_CalendarSync
.LoadMap();
281 if( env
->m_ContactsSync
.m_Sync
) {
282 env
->m_ContactsSync
.LoadCache();
283 env
->m_ContactsSync
.LoadMap();
289 // Don't let C++ exceptions escape to the C code
290 catch( std::bad_alloc
&ba
) {
291 osync_error_update(error
, "Unable to allocate memory for environment: %s", ba
.what());
295 catch( std::exception
&e
) {
296 osync_error_update(error
, "%s", e
.what());
302 static void connect(OSyncContext
*ctx
)
304 Trace
trace("connect");
308 // Each time you get passed a context (which is used to track
309 // calls to your plugin) you can get the data your returned in
310 // initialize via this call:
311 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
313 // Probe for available devices
315 int nIndex
= probe
.FindActive(env
->m_pin
);
317 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, "Unable to find PIN %lx", env
->m_pin
);
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
);
345 OSyncMember
*member
= osync_context_get_member(ctx
);
347 if( env
->m_CalendarSync
.m_Sync
&& osync_member_objtype_enabled(member
, "event") ) {
348 GetChanges(ctx
, env
, &env
->m_CalendarSync
,
349 "Calendar", "event", "vevent20",
350 &VEventConverter::GetRecordData
);
353 if( env
->m_ContactsSync
.m_Sync
&& osync_member_objtype_enabled(member
, "contact") ) {
354 GetChanges(ctx
, env
, &env
->m_ContactsSync
,
355 "Address Book", "contact", "vcard30",
356 &VCardConverter::GetRecordData
);
360 osync_context_report_success(ctx
);
362 // don't let exceptions escape to the C modules
363 catch( std::exception
&e
) {
364 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
368 static osync_bool
commit_change(OSyncContext
*ctx
, OSyncChange
*change
)
370 Trace
trace("commit_change");
372 // We can rely on a valid record state table, since get_changeinfo()
373 // will be called first, and will fill the table.
377 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
379 // find the needed commit function, based on objtype of the change
380 CommitData_t CommitData
= GetCommitFunction(change
);
382 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
383 "unable to get commit function pointer");
387 // find the matching cache, state table, and id map for this change
388 DatabaseSyncState
*pSync
= env
->GetSyncObject(change
);
390 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
391 "unable to get sync object that matches change type");
395 // is syncing turned on for this type?
396 if( !pSync
->m_Sync
) {
397 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
398 "This object type is disabled in the barry-sync config");
402 // make references instead of pointers
403 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
404 Barry::RecordStateTable
&table
= pSync
->m_Table
;
405 idmap
&map
= pSync
->m_IdMap
;
406 Barry::Mode::Desktop
&desktop
= *env
->GetDesktop();
407 unsigned int dbId
= pSync
->m_dbId
;
410 // extract RecordId from change's UID,
411 // and update the ID map if necessary
412 const char *uid
= osync_change_get_uid(change
);
413 trace
.logf("uid from change: %s", uid
);
414 if( strlen(uid
) == 0 ) {
415 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
416 "uid from change object is blank!");
418 unsigned long RecordId
= pSync
->GetMappedRecordId(uid
);
420 // search for the RecordId in the state table, to find the
421 // index... we only need the index if we are deleting or
423 Barry::RecordStateTable::IndexType StateIndex
;
424 if( osync_change_get_changetype(change
) != CHANGE_ADDED
) {
425 if( !table
.GetIndex(RecordId
, &StateIndex
) ) {
426 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
427 "unable to get state table index for RecordId: %lu",
436 switch( osync_change_get_changetype(change
) )
439 desktop
.DeleteRecord(dbId
, StateIndex
);
440 cache
.erase(RecordId
);
445 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
446 osync_change_get_data(change
), true, errmsg
);
448 trace
.logf("CommitData() for ADDED state returned false: %s", errmsg
.c_str());
449 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
453 cache
[RecordId
] = false;
456 case CHANGE_MODIFIED
:
457 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
458 osync_change_get_data(change
), false, errmsg
);
460 trace
.logf("CommitData() for MODIFIED state returned false: %s", errmsg
.c_str());
461 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
468 trace
.log("Unknown change type");
469 osync_debug("barry-sync", 0, "Unknown change type");
474 osync_context_report_success(ctx
);
479 catch( std::exception
&e
) {
480 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
482 // we don't worry about unmapping ids here, as there
483 // is still a possibility that the record was added...
484 // plus, the map might not get written out to disk anyway
485 // in a plugin error state
491 static void sync_done(OSyncContext
*ctx
)
494 // This function will only be called if the sync was successfull
497 Trace
trace("sync_done");
501 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
503 // we reconnect to the device here, since dirty flags
504 // for records we've just touched do not show up until
505 // a disconnect... as far as I can tell.
508 // do cleanup for each database
509 if( FinishSync(ctx
, env
, &env
->m_CalendarSync
) &&
510 FinishSync(ctx
, env
, &env
->m_ContactsSync
) )
513 osync_context_report_success(ctx
);
517 catch( std::exception
&e
) {
518 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
522 static void disconnect(OSyncContext
*ctx
)
524 Trace
trace("disconnect");
526 // Disconnect the controller, which closes our connection
527 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
531 osync_context_report_success(ctx
);
534 static void finalize(void *data
)
536 Trace
trace("finalize");
538 BarryEnvironment
*env
= (BarryEnvironment
*)data
;
542 void get_info(OSyncEnv
*env
)
544 Trace
trace("get_info");
546 // Create first plugin
547 OSyncPluginInfo
*info
= osync_plugin_new_info(env
);
549 info
->name
= "barry-sync";
550 info
->longname
= "Barry OpenSync plugin v0.18.0 for the Blackberry handheld";
551 info
->description
= "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
552 info
->version
= 1; // API version (opensync api?)
553 info
->is_threadsafe
= TRUE
;
555 info
->functions
.initialize
= initialize
;
556 info
->functions
.connect
= connect
;
557 info
->functions
.sync_done
= sync_done
;
558 info
->functions
.disconnect
= disconnect
;
559 info
->functions
.finalize
= finalize
;
560 info
->functions
.get_changeinfo
= get_changeinfo
;
562 // If you like, you can overwrite the default timeouts of your plugin
563 // The default is set to 60 sec. Note that this MUST NOT be used to
564 // wait for expected timeouts (Lets say while waiting for a webserver).
565 // you should wait for the normal timeout and return a error.
566 // info->timeouts.connect_timeout = 5;
567 // There are more timeouts for the other functions
570 // Register each supported feature
573 // Calendar entries, using batch commit
574 osync_plugin_accept_objtype(info
, "event");
575 osync_plugin_accept_objformat(info
, "event", "vevent20", NULL
);
576 osync_plugin_set_commit_objformat(info
, "event", "vevent20",
579 // Address Book entries
580 osync_plugin_accept_objtype(info
, "contact");
581 osync_plugin_accept_objformat(info
, "contact", "vcard30", NULL
);
582 osync_plugin_set_commit_objformat(info
, "contact", "vcard30",