3 // Opensync module for the USB Blackberry handheld
7 Copyright (C) 2006-2007, 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_sync.h"
25 #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 batch_commit_vevent20(OSyncContext
*ctx
,
41 OSyncContext
**contexts
,
42 OSyncChange
**changes
);
43 static void sync_done(OSyncContext
*ctx
);
44 static void disconnect(OSyncContext
*ctx
);
45 static void finalize(void *data
);
46 void get_info(OSyncEnv
*env
);
50 //////////////////////////////////////////////////////////////////////////////
52 // Support functions and classes
55 void GetChanges(OSyncContext
*ctx
, BarryEnvironment
*env
,
56 BarryEnvironment::cache_type
&cache
,
57 const char *DBDBName
, const char *FormatName
,
60 Trace
trace("GetChanges");
62 // shortcut references
63 using namespace Barry
;
64 using Barry::RecordStateTable
;
65 Controller
&con
= *env
->m_pCon
;
68 unsigned int dbId
= con
.GetDBID(DBDBName
);
69 RecordStateTable table
;
70 con
.GetRecordStateTable(dbId
, table
);
72 // cycle through the state table...
73 // - if not in cache, it is added.
74 // - if in cache, check Blackberry's dirty flag
75 RecordStateTable::StateMapType::const_iterator i
= table
.StateMap
.begin();
76 for( ; i
!= table
.StateMap
.end(); ++i
) {
78 OSyncChange
*change
= 0;
79 const RecordStateTable::IndexType
&index
= i
->first
;
80 const RecordStateTable::State
&state
= i
->second
;
83 BarryEnvironment::cache_type::const_iterator c
= cache
.find(state
.RecordId
);
84 if( c
== cache
.end() ) {
85 // not in cache, this is a new item
86 trace
.log("found an ADDED change");
87 change
= osync_change_new();
88 osync_change_set_changetype(change
, CHANGE_ADDED
);
91 // in the cache... dirty?
94 trace
.log("found a MODIFIED change");
95 change
= osync_change_new();
96 osync_change_set_changetype(change
, CHANGE_MODIFIED
);
99 trace
.log("no change detected");
103 // finish filling out the change object
105 osync_change_set_member(change
, env
->member
);
106 osync_change_set_objformat_string(change
, FormatName
);
108 char *uid
= g_strdup_printf("%u", state
.RecordId
);
109 osync_change_set_uid(change
, uid
);
110 trace
.logf("change record ID: %s", uid
);
113 // Now you can set the data for the object
114 // Set the last argument to FALSE if the real data
115 // should be queried later in a "get_data" function
116 char *data
= (*getdata
)(env
, dbId
, index
);
117 osync_change_set_data(change
, data
, strlen(data
), TRUE
);
119 // just report the change via
120 osync_context_report_change(ctx
, change
);
124 // now cycle through the cache... any objects in the cache
125 // but not found in the state table means that they have been
126 // deleted in the device
127 BarryEnvironment::cache_type::const_iterator c
= cache
.begin();
128 for( ; c
!= cache
.end(); ++c
) {
129 uint32_t recordId
= c
->first
;
131 // search the state table
132 i
= table
.StateMap
.begin();
133 for( ; i
!= table
.StateMap
.end(); ++i
) {
135 if( i
->second
.RecordId
== recordId
)
139 // check if not found...
140 if( i
== table
.StateMap
.end() ) {
141 // register a DELETE, no data
142 trace
.log("found DELETE change");
144 OSyncChange
*change
= osync_change_new();
145 osync_change_set_changetype(change
, CHANGE_DELETED
);
146 osync_change_set_member(change
, env
->member
);
147 osync_change_set_objformat_string(change
, FormatName
);
149 char *uid
= g_strdup_printf("%u", recordId
);
150 osync_change_set_uid(change
, uid
);
155 osync_context_report_change(ctx
, change
);
159 // finally, cycle through the state map again, and overwrite the
160 // cache with the current state table. Memory only... if successful,
161 // it will be written back to disk later on.
166 for( i
= table
.StateMap
.begin(); i
!= table
.StateMap
.end(); ++i
) {
167 const RecordStateTable::State
&state
= i
->second
;
168 cache
[state
.RecordId
] = false;
172 CommitData_t
GetCommitFunction(OSyncChange
*change
)
174 OSyncObjType
*type
= osync_change_get_objtype(change
);
175 const char *name
= osync_objtype_get_name(type
);
176 if( strcmp(name
, "event") == 0 ) {
177 return &VEventConverter::CommitRecordData
;
179 else if( strcmp(name
, "contact") == 0 ) {
181 // return &VCardConverter::CommitRecordData;
188 BarryEnvironment::cache_type
*GetCache(BarryEnvironment
*env
, 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 &env
->m_CalendarCache
;
195 else if( strcmp(name
, "contact") == 0 ) {
196 return &env
->m_ContactsCache
;
204 bool CommitChange(BarryEnvironment
*env
,
209 Trace
trace("CommitChange");
211 // find the needed commit function, based on objtype of the change
212 CommitData_t CommitData
= GetCommitFunction(change
);
214 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
215 "unable to get commit function pointer");
219 BarryEnvironment::cache_type
*pCache
= GetCache(env
, change
);
221 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
222 "unable to get cache");
228 Barry::Controller
&con
= *env
->m_pCon
;
229 Barry::RecordStateTable
&table
= env
->m_CalendarTable
;
231 // extract RecordId from change's UID
232 // FIXME - this may need some fudging, since there is no
233 // guarantee that the UID will be a plain number
234 const char *uid
= osync_change_get_uid(change
);
235 trace
.logf("uid from change: %s", uid
);
236 unsigned long RecordId
;
237 if( sscanf(uid
, "%lu", &RecordId
) == 0 ) {
238 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
,
239 "uid is not an expected number: %s", uid
);
242 trace
.logf("parsed uid as: %lu", RecordId
);
244 // search for the RecordId in the state table, to find the
245 // index... we only need the index if we are deleting or
247 Barry::RecordStateTable::IndexType StateIndex
;
248 if( osync_change_get_changetype(change
) != CHANGE_ADDED
) {
249 if( !table
.GetIndex(RecordId
, &StateIndex
) ) {
250 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
251 "unable to get state table index for RecordId: %lu",
259 switch( osync_change_get_changetype(change
) )
262 con
.DeleteRecord(dbId
, StateIndex
);
263 pCache
->erase(RecordId
);
267 if( !(*CommitData
)(env
, dbId
, StateIndex
, osync_change_get_data(change
), true, errmsg
) ) {
268 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
271 (*pCache
)[RecordId
] = false;
274 case CHANGE_MODIFIED
:
275 if( !(*CommitData
)(env
, dbId
, StateIndex
, osync_change_get_data(change
), false, errmsg
) ) {
276 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
282 osync_debug("barry-sync", 0, "Unknown change type");
287 osync_context_report_success(ctx
);
291 catch( std::exception
&e
) {
292 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
300 //////////////////////////////////////////////////////////////////////////////
305 static void *initialize(OSyncMember
*member
, OSyncError
**error
)
307 Trace
trace("initialize");
309 BarryEnvironment
*env
= 0;
311 // Create the environment struct, including our Barry objects
313 // FIXME - near the end of release, do a run with
314 // this set to true, and look for USB protocol
318 env
= new BarryEnvironment(member
);
320 // Load config file for this plugin
323 if (!osync_member_get_config(member
, &configdata
, &configsize
, error
)) {
324 osync_error_update(error
, "Unable to get config data: %s",
325 osync_error_print(error
));
330 // Process the configdata here and set the options on your environment
331 env
->ParseConfig(configdata
, configsize
);
334 // Load all needed cache files
335 if( env
->m_SyncCalendar
) {
336 env
->LoadCalendarCache();
339 if( env
->m_SyncContacts
) {
340 env
->LoadContactsCache();
346 // Don't let C++ exceptions escape to the C code
347 catch( std::bad_alloc
&ba
) {
348 osync_error_update(error
, "Unable to allocate memory for environment: %s", ba
.what());
352 catch( std::exception
&e
) {
353 osync_error_update(error
, "%s", e
.what());
359 static void connect(OSyncContext
*ctx
)
361 Trace
trace("connect");
365 // Each time you get passed a context (which is used to track
366 // calls to your plugin) you can get the data your returned in
367 // initialize via this call:
368 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
370 // Probe for available devices
372 int nIndex
= probe
.FindActive(env
->m_pin
);
374 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, "Unable to find PIN %lu", env
->m_pin
);
377 env
->m_ProbeResult
= probe
.Get(nIndex
);
380 env
->m_pCon
= new Barry::Controller(env
->m_ProbeResult
);
381 env
->m_pCon
->OpenMode(Barry::Controller::Desktop
);
384 osync_context_report_success(ctx
);
387 // Don't let exceptions escape to the C modules
388 catch( std::bad_alloc
&ba
) {
389 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
390 "Unable to allocate memory for controller: %s", ba
.what());
392 catch( std::exception
&e
) {
393 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
398 static void get_changeinfo(OSyncContext
*ctx
)
400 Trace
trace("get_changeinfo");
404 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
406 if( env
->m_SyncCalendar
) {
407 GetChanges(ctx
, env
, env
->m_CalendarCache
,
408 "Calendar", "vevent20",
409 &VEventConverter::GetRecordData
);
412 if( env
->m_SyncContacts
) {
413 // FIXME - not yet implemented
414 // GetChanges(ctx, env, env->m_ContactsCache,
415 // "Address Book", "vcard30",
416 // &ContactToVCard::GetRecordData);
420 osync_context_report_success(ctx
);
422 // don't let exceptions escape to the C modules
423 catch( std::exception
&e
) {
424 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
428 // this function will be called exactly once with all objects to write
429 // gathered in an array
430 static void batch_commit_vevent20(OSyncContext
*ctx
,
431 OSyncContext
**contexts
,
432 OSyncChange
**changes
)
434 Trace
trace("batch_commit_vevent20");
436 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
439 Barry::Controller
&con
= *env
->m_pCon
;
442 unsigned int dbId
= con
.GetDBID("Calendar");
443 con
.GetRecordStateTable(dbId
, env
->m_CalendarTable
);
446 // Cycle through changes, looking for modifications first,
447 // so that the state table will be stable (paranoia here,
448 // it's worth checking whether this is necessary, by
449 // trying to overwrite, delete, add, then overwrite
450 // another using the same table index... will it work?)
452 // Update: tests confirm that it does work, for
455 for( int i
= 0; contexts
[i
] && changes
[i
]; i
++ ) {
456 if( osync_change_get_changetype(changes
[i
]) != CHANGE_ADDED
) {
457 if( !CommitChange(env
, dbId
, contexts
[i
], changes
[i
]) ) {
458 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
459 "error committing context %i", i
);
464 for( int i
= 0; contexts
[i
] && changes
[i
]; i
++ ) {
465 if( osync_change_get_changetype(changes
[i
]) == CHANGE_ADDED
) {
466 if( !CommitChange(env
, dbId
, contexts
[i
], changes
[i
]) ) {
467 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
468 "error committing context %i", i
);
474 // get the state table again, so we can update
475 // the cache on success, later
476 con
.GetRecordStateTable(dbId
, env
->m_CalendarTable
);
479 osync_context_report_success(ctx
);
482 catch( std::exception
&e
) {
483 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
487 static void sync_done(OSyncContext
*ctx
)
490 // This function will only be called if the sync was successfull
493 Trace
trace("sync_done");
497 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
499 if( env
->m_SyncCalendar
) {
501 if( !env
->SaveCalendarCache() ) {
502 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
503 "Error saving calendar cache");
506 // clear all dirty flags
507 env
->ClearCalendarDirtyFlags();
510 if( env
->m_SyncContacts
) {
512 if( !env
->SaveContactsCache() ) {
513 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
514 "Error saving contacts cache");
517 // clear all dirty flags
518 env
->ClearContactsDirtyFlags();
522 osync_context_report_success(ctx
);
525 catch( std::exception
&e
) {
526 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
530 static void disconnect(OSyncContext
*ctx
)
532 Trace
trace("disconnect");
534 // Disconnect the controller, which closes our connection
535 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
539 osync_context_report_success(ctx
);
542 static void finalize(void *data
)
544 Trace
trace("finalize");
546 BarryEnvironment
*env
= (BarryEnvironment
*)data
;
550 void get_info(OSyncEnv
*env
)
552 Trace
trace("get_info");
554 // Create first plugin
555 OSyncPluginInfo
*info
= osync_plugin_new_info(env
);
557 info
->name
= "barry-sync";
558 info
->longname
= "Barry OpenSync plugin for the Blackberry handheld";
559 info
->description
= "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
560 info
->version
= 1; // API version (opensync api?)
561 info
->is_threadsafe
= TRUE
;
563 info
->functions
.initialize
= initialize
;
564 info
->functions
.connect
= connect
;
565 info
->functions
.sync_done
= sync_done
;
566 info
->functions
.disconnect
= disconnect
;
567 info
->functions
.finalize
= finalize
;
568 info
->functions
.get_changeinfo
= get_changeinfo
;
570 // If you like, you can overwrite the default timeouts of your plugin
571 // The default is set to 60 sec. Note that this MUST NOT be used to
572 // wait for expected timeouts (Lets say while waiting for a webserver).
573 // you should wait for the normal timeout and return a error.
574 // info->timeouts.connect_timeout = 5;
575 // There are more timeouts for the other functions
578 // Register each supported feature
581 // Calendar entries, using batch commit
582 osync_plugin_accept_objtype(info
, "event");
583 osync_plugin_accept_objformat(info
, "event", "vevent20", NULL
);
584 osync_plugin_set_batch_commit_objformat(info
, "event", "vevent20",
585 batch_commit_vevent20
);
587 // Address Book entries
588 // osync_plugin_accept_objtype(info, "contact");
589 // osync_plugin_accept_objformat(info, "contact", "vcard30", NULL);
590 // osync_plugin_set_batch_commit_objformat(info, "contact", "vcard30",
591 // batch_commit_vcard30);