3 // Opensync module for the USB Blackberry handheld
7 Copyright (C) 2006-2013, 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"
38 // All functions that are callable from outside must look like C
40 static void *initialize(OSyncMember
*member
, OSyncError
**error
);
41 static void connect(OSyncContext
*ctx
);
42 static void get_changeinfo(OSyncContext
*ctx
);
43 static void sync_done(OSyncContext
*ctx
);
44 static void disconnect(OSyncContext
*ctx
);
45 static void finalize(void *data
);
46 BXEXPORT
void get_info(OSyncEnv
*env
);
50 //////////////////////////////////////////////////////////////////////////////
52 // Support functions and classes
56 void GetChanges(OSyncContext
*ctx
, BarryEnvironment
*env
,
57 DatabaseSyncState
*pSync
,
59 const char *ObjTypeName
, const char *FormatName
,
62 Trace
trace("GetChanges");
64 // shortcut references
65 using namespace Barry
;
66 using Barry::RecordStateTable
;
67 Mode::Desktop
&desktop
= *env
->GetDesktop();
69 // find the matching cache, state table, and id map for this change
70 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
71 idmap
&map
= pSync
->m_IdMap
;
73 // check if slow sync has been requested, and if so, empty the
74 // cache and id map and start fresh
75 if( osync_member_get_slow_sync(env
->member
, ObjTypeName
) ) {
76 trace
.log(_("GetChanges: slow sync request detected, clearing cache and id map"));
82 unsigned int dbId
= desktop
.GetDBID(DBDBName
);
83 RecordStateTable
&table
= pSync
->m_Table
;
84 desktop
.GetRecordStateTable(dbId
, table
);
86 // cycle through the state table...
87 // - if not in cache, it is added.
88 // - if in cache, check Blackberry's dirty flag
89 RecordStateTable::StateMapType::const_iterator i
= table
.StateMap
.begin();
90 for( ; i
!= table
.StateMap
.end(); ++i
) {
92 OSyncChange
*change
= 0;
93 const RecordStateTable::IndexType
&index
= i
->first
;
94 const RecordStateTable::State
&state
= i
->second
;
96 // search the idmap for the UID
97 std::string uid
= pSync
->Map2Uid(state
.RecordId
);
100 DatabaseSyncState::cache_type::const_iterator c
= cache
.find(state
.RecordId
);
101 if( c
== cache
.end() ) {
102 // not in cache, this is a new item
103 trace
.log(_("found an ADDED change"));
104 change
= osync_change_new();
105 osync_change_set_changetype(change
, CHANGE_ADDED
);
108 // in the cache... dirty?
111 trace
.log(_("found a MODIFIED change"));
112 change
= osync_change_new();
113 osync_change_set_changetype(change
, CHANGE_MODIFIED
);
116 trace
.log(_("no change detected"));
120 // finish filling out the change object
122 osync_change_set_member(change
, env
->member
);
123 osync_change_set_objformat_string(change
, FormatName
);
125 osync_change_set_uid(change
, uid
.c_str());
126 trace
.logf(_("change record ID: %s"), uid
.c_str());
128 // Now you can set the data for the object
129 // Set the last argument to FALSE if the real data
130 // should be queried later in a "get_data" function
131 char *data
= (*getdata
)(env
, dbId
, index
);
132 osync_change_set_data(change
, data
, strlen(data
), TRUE
);
134 // just report the change via
135 osync_context_report_change(ctx
, change
);
137 // map our IDs for later
138 map
.Map(uid
, state
.RecordId
);
142 // now cycle through the cache... any objects in the cache
143 // but not found in the state table means that they have been
144 // deleted in the device
145 DatabaseSyncState::cache_type::const_iterator c
= cache
.begin();
146 for( ; c
!= cache
.end(); ++c
) {
147 uint32_t recordId
= c
->first
;
149 // search the idmap for the UID
150 std::string uid
= pSync
->Map2Uid(recordId
);
152 // search the state table
153 i
= table
.StateMap
.begin();
154 for( ; i
!= table
.StateMap
.end(); ++i
) {
156 if( i
->second
.RecordId
== recordId
)
160 // check if not found...
161 if( i
== table
.StateMap
.end() ) {
162 // register a DELETE, no data
163 trace
.log(_("found DELETE change"));
165 OSyncChange
*change
= osync_change_new();
166 osync_change_set_changetype(change
, CHANGE_DELETED
);
167 osync_change_set_member(change
, env
->member
);
168 osync_change_set_objformat_string(change
, FormatName
);
170 osync_change_set_uid(change
, uid
.c_str());
171 trace
.log(uid
.c_str());
174 osync_context_report_change(ctx
, change
);
178 // finally, cycle through the state map again, and overwrite the
179 // cache with the current state table. Memory only... if successful,
180 // it will be written back to disk later on.
185 for( i
= table
.StateMap
.begin(); i
!= table
.StateMap
.end(); ++i
) {
186 const RecordStateTable::State
&state
= i
->second
;
187 cache
[state
.RecordId
] = false;
191 CommitData_t
GetCommitFunction(OSyncChange
*change
)
193 OSyncObjType
*type
= osync_change_get_objtype(change
);
194 const char *name
= osync_objtype_get_name(type
);
195 if( strcmp(name
, "event") == 0 ) {
196 return &VEventConverter::CommitRecordData
;
198 else if( strcmp(name
, "contact") == 0 ) {
199 return &VCardConverter::CommitRecordData
;
206 bool FinishSync(OSyncContext
*ctx
, BarryEnvironment
*env
, DatabaseSyncState
*pSync
)
208 Trace
trace("FinishSync()");
210 if( !pSync
->m_Sync
) {
211 // this mode is disabled in config, skip
215 Barry::Mode::Desktop
&desktop
= *env
->GetDesktop();
217 // get the state table again, so we can update
218 // the cache properly
219 desktop
.GetRecordStateTable(pSync
->m_dbId
, pSync
->m_Table
);
222 if( !pSync
->SaveCache() ) {
223 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
224 _("Error saving calendar cache"));
230 if( !pSync
->SaveMap() ) {
231 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
,
232 _("Error saving calendar id map"));
236 // clear all dirty flags in device
237 env
->ClearDirtyFlags(pSync
->m_Table
, pSync
->m_dbName
);
243 //////////////////////////////////////////////////////////////////////////////
248 static void *initialize(OSyncMember
*member
, OSyncError
**error
)
250 Trace
trace("initialize");
252 BarryEnvironment
*env
= 0;
254 // Create the environment struct, including our Barry objects
256 env
= new BarryEnvironment(member
);
258 // Load config file for this plugin
261 if (!osync_member_get_config(member
, &configdata
, &configsize
, error
)) {
262 osync_error_update(error
, _("Unable to get config data: %s"),
263 osync_error_print(error
));
268 // Process the configdata here and set the options on your environment
269 env
->ParseConfig(configdata
, configsize
);
272 // FIXME - near the end of release, do a run with
273 // this set to true, and look for USB protocol
275 Barry::Init(env
->m_DebugMode
);
277 // Load all needed cache files
278 if( env
->m_CalendarSync
.m_Sync
) {
279 env
->m_CalendarSync
.LoadCache();
280 env
->m_CalendarSync
.LoadMap();
283 if( env
->m_ContactsSync
.m_Sync
) {
284 env
->m_ContactsSync
.LoadCache();
285 env
->m_ContactsSync
.LoadMap();
291 // Don't let C++ exceptions escape to the C code
292 catch( std::bad_alloc
&ba
) {
293 osync_error_update(error
, _("Unable to allocate memory for environment: %s"), ba
.what());
297 catch( std::exception
&e
) {
298 osync_error_update(error
, "%s", e
.what());
304 static void connect(OSyncContext
*ctx
)
306 Trace
trace("connect");
310 // Each time you get passed a context (which is used to track
311 // calls to your plugin) you can get the data your returned in
312 // initialize via this call:
313 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
315 // Probe for available devices
317 int nIndex
= probe
.FindActive(env
->m_pin
);
319 osync_context_report_error(ctx
, OSYNC_ERROR_NO_CONNECTION
, _("Unable to find PIN %lx"), env
->m_pin
);
323 env
->Connect(probe
.Get(nIndex
));
326 osync_context_report_success(ctx
);
329 // Don't let exceptions escape to the C modules
330 catch( std::bad_alloc
&ba
) {
331 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
332 _("Unable to allocate memory for controller: %s"), ba
.what());
334 catch( std::exception
&e
) {
335 osync_context_report_error(ctx
, OSYNC_ERROR_INITIALIZATION
,
340 static void get_changeinfo(OSyncContext
*ctx
)
342 Trace
trace("get_changeinfo");
346 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
347 OSyncMember
*member
= osync_context_get_member(ctx
);
349 if( env
->m_CalendarSync
.m_Sync
&& osync_member_objtype_enabled(member
, "event") ) {
350 GetChanges(ctx
, env
, &env
->m_CalendarSync
,
351 "Calendar", "event", "vevent20",
352 &VEventConverter::GetRecordData
);
355 if( env
->m_ContactsSync
.m_Sync
&& osync_member_objtype_enabled(member
, "contact") ) {
356 GetChanges(ctx
, env
, &env
->m_ContactsSync
,
357 "Address Book", "contact", "vcard30",
358 &VCardConverter::GetRecordData
);
362 osync_context_report_success(ctx
);
364 // don't let exceptions escape to the C modules
365 catch( std::exception
&e
) {
366 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
370 static osync_bool
commit_change(OSyncContext
*ctx
, OSyncChange
*change
)
372 Trace
trace("commit_change");
374 // We can rely on a valid record state table, since get_changeinfo()
375 // will be called first, and will fill the table.
379 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
381 // find the needed commit function, based on objtype of the change
382 CommitData_t CommitData
= GetCommitFunction(change
);
384 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
385 _("unable to get commit function pointer"));
389 // find the matching cache, state table, and id map for this change
390 DatabaseSyncState
*pSync
= env
->GetSyncObject(change
);
392 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
393 _("unable to get sync object that matches change type"));
397 // is syncing turned on for this type?
398 if( !pSync
->m_Sync
) {
399 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
400 _("This object type is disabled in the barry-sync config"));
404 // make references instead of pointers
405 DatabaseSyncState::cache_type
&cache
= pSync
->m_Cache
;
406 Barry::RecordStateTable
&table
= pSync
->m_Table
;
407 idmap
&map
= pSync
->m_IdMap
;
408 Barry::Mode::Desktop
&desktop
= *env
->GetDesktop();
409 unsigned int dbId
= pSync
->m_dbId
;
412 // extract RecordId from change's UID,
413 // and update the ID map if necessary
414 const char *uid
= osync_change_get_uid(change
);
415 trace
.logf("uid from change: %s", uid
);
416 if( strlen(uid
) == 0 ) {
417 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
418 _("uid from change object is blank!"));
420 unsigned long RecordId
= pSync
->GetMappedRecordId(uid
);
422 // search for the RecordId in the state table, to find the
423 // index... we only need the index if we are deleting or
425 Barry::RecordStateTable::IndexType StateIndex
;
426 if( osync_change_get_changetype(change
) != CHANGE_ADDED
) {
427 if( !table
.GetIndex(RecordId
, &StateIndex
) ) {
428 osync_context_report_error(ctx
, OSYNC_ERROR_GENERIC
,
429 _("unable to get state table index for RecordId: %lu"),
438 switch( osync_change_get_changetype(change
) )
441 desktop
.DeleteRecord(dbId
, StateIndex
);
442 cache
.erase(RecordId
);
447 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
448 osync_change_get_data(change
), true, errmsg
);
450 trace
.logf(_("CommitData() for ADDED state returned false: %s"), errmsg
.c_str());
451 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
455 cache
[RecordId
] = false;
458 case CHANGE_MODIFIED
:
459 status
= (*CommitData
)(env
, dbId
, StateIndex
, RecordId
,
460 osync_change_get_data(change
), false, errmsg
);
462 trace
.logf(_("CommitData() for MODIFIED state returned false: %s"), errmsg
.c_str());
463 osync_context_report_error(ctx
, OSYNC_ERROR_PARAMETER
, "%s", errmsg
.c_str());
470 trace
.log(_("Unknown change type"));
471 osync_debug("barry-sync", 0, _("Unknown change type"));
476 osync_context_report_success(ctx
);
481 catch( std::exception
&e
) {
482 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
484 // we don't worry about unmapping ids here, as there
485 // is still a possibility that the record was added...
486 // plus, the map might not get written out to disk anyway
487 // in a plugin error state
493 static void sync_done(OSyncContext
*ctx
)
496 // This function will only be called if the sync was successfull
499 Trace
trace("sync_done");
503 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
505 // we reconnect to the device here, since dirty flags
506 // for records we've just touched do not show up until
507 // a disconnect... as far as I can tell.
510 // do cleanup for each database
511 if( FinishSync(ctx
, env
, &env
->m_CalendarSync
) &&
512 FinishSync(ctx
, env
, &env
->m_ContactsSync
) )
515 osync_context_report_success(ctx
);
519 catch( std::exception
&e
) {
520 osync_context_report_error(ctx
, OSYNC_ERROR_IO_ERROR
, "%s", e
.what());
524 static void disconnect(OSyncContext
*ctx
)
526 Trace
trace("disconnect");
528 // Disconnect the controller, which closes our connection
529 BarryEnvironment
*env
= (BarryEnvironment
*)osync_context_get_plugin_data(ctx
);
533 osync_context_report_success(ctx
);
536 static void finalize(void *data
)
538 Trace
trace("finalize");
540 BarryEnvironment
*env
= (BarryEnvironment
*)data
;
544 void get_info(OSyncEnv
*env
)
546 Trace
trace("get_info");
548 static bool i18n_initialized
= false;
549 if( !i18n_initialized
) {
550 // initialize i18n gettext directory
551 // the rest is done in i18n.h
552 setlocale(LC_ALL
, "");
553 bindtextdomain(PACKAGE
, LOCALEDIR
);
555 i18n_initialized
= true;
558 // Create first plugin
559 OSyncPluginInfo
*info
= osync_plugin_new_info(env
);
561 // not translation for these strings, as I think they are const,
562 // and the info struct relies on their existence
563 info
->name
= "barry-sync";
564 info
->longname
= "Barry OpenSync plugin v" PACKAGE_VERSION
" for the Blackberry handheld";
565 info
->description
= "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds";
566 info
->version
= 1; // API version (opensync api?)
567 info
->is_threadsafe
= TRUE
;
569 info
->functions
.initialize
= initialize
;
570 info
->functions
.connect
= connect
;
571 info
->functions
.sync_done
= sync_done
;
572 info
->functions
.disconnect
= disconnect
;
573 info
->functions
.finalize
= finalize
;
574 info
->functions
.get_changeinfo
= get_changeinfo
;
576 // If you like, you can overwrite the default timeouts of your plugin
577 // The default is set to 60 sec. Note that this MUST NOT be used to
578 // wait for expected timeouts (Lets say while waiting for a webserver).
579 // you should wait for the normal timeout and return a error.
580 // info->timeouts.connect_timeout = 5;
581 // There are more timeouts for the other functions
584 // Register each supported feature
587 // Calendar entries, using batch commit
588 osync_plugin_accept_objtype(info
, "event");
589 osync_plugin_accept_objformat(info
, "event", "vevent20", NULL
);
590 osync_plugin_set_commit_objformat(info
, "event", "vevent20",
593 // Address Book entries
594 osync_plugin_accept_objtype(info
, "contact");
595 osync_plugin_accept_objformat(info
, "contact", "vcard30", NULL
);
596 osync_plugin_set_commit_objformat(info
, "contact", "vcard30",