lib: set ProbeResult's constructor to private
[barry.git] / opensync-plugin / src / barry_sync.cc
blob2c2ad687db17245071f73afef26446aa4b1fe2ea
1 //
2 // \file barry_sync.cc
3 // Opensync module for the USB Blackberry handheld
4 //
6 /*
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"
27 #include "vevent.h"
28 #include "vcard.h"
29 #include "trace.h"
30 #include <string>
31 #include <glib.h>
32 #include <stdint.h>
33 #include <string.h>
35 // All functions that are callable from outside must look like C
36 extern "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,
55 const char *DBDBName,
56 const char *ObjTypeName, const char *FormatName,
57 GetData_t getdata)
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");
74 cache.clear();
75 map.clear();
78 // fetch state table
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);
96 // search the cache
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);
104 else {
105 // in the cache... dirty?
106 if( state.Dirty ) {
107 // modified
108 trace.log("found a MODIFIED change");
109 change = osync_change_new();
110 osync_change_set_changetype(change, CHANGE_MODIFIED);
112 else {
113 trace.log("no change detected");
117 // finish filling out the change object
118 if( change ) {
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 )
154 break; // found
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());
170 // report the change
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.
179 // start fresh
180 cache.clear();
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;
198 else {
199 return 0;
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
209 return true;
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);
218 // update the cache
219 if( !pSync->SaveCache() ) {
220 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
221 "Error saving calendar cache");
222 return false;
225 // save the id map
226 pSync->CleanupMap();
227 if( !pSync->SaveMap() ) {
228 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR,
229 "Error saving calendar id map");
230 return false;
233 // clear all dirty flags in device
234 env->ClearDirtyFlags(pSync->m_Table, pSync->m_dbName);
235 return true;
240 //////////////////////////////////////////////////////////////////////////////
242 // OpenSync API
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
252 try {
253 env = new BarryEnvironment(member);
255 // Load config file for this plugin
256 char *configdata;
257 int configsize;
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));
261 delete env;
262 return NULL;
265 // Process the configdata here and set the options on your environment
266 env->ParseConfig(configdata, configsize);
267 free(configdata);
269 // FIXME - near the end of release, do a run with
270 // this set to true, and look for USB protocol
271 // inefficiencies.
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();
285 return env;
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());
291 delete env;
292 return NULL;
294 catch( std::exception &e ) {
295 osync_error_update(error, "%s", e.what());
296 delete env;
297 return NULL;
301 static void connect(OSyncContext *ctx)
303 Trace trace("connect");
305 try {
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
313 Barry::Probe probe;
314 int nIndex = probe.FindActive(env->m_pin);
315 if( nIndex == -1 ) {
316 osync_context_report_error(ctx, OSYNC_ERROR_NO_CONNECTION, "Unable to find PIN %lx", env->m_pin);
317 return;
319 env->m_ProbeResult.reset( new Barry::ProbeResult(probe.Get(nIndex)) );
321 env->Connect(probe.Get(nIndex));
323 // Success!
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,
334 "%s", e.what());
338 static void get_changeinfo(OSyncContext *ctx)
340 Trace trace("get_changeinfo");
342 try {
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);
358 // Success!
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.
374 try {
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);
380 if( !CommitData ) {
381 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
382 "unable to get commit function pointer");
383 return false;
386 // find the matching cache, state table, and id map for this change
387 DatabaseSyncState *pSync = env->GetSyncObject(change);
388 if( !pSync ) {
389 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
390 "unable to get sync object that matches change type");
391 return false;
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");
398 return false;
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
421 // modifying
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",
427 RecordId);
428 return false;
432 std::string errmsg;
433 bool status;
435 switch( osync_change_get_changetype(change) )
437 case CHANGE_DELETED:
438 desktop.DeleteRecord(dbId, StateIndex);
439 cache.erase(RecordId);
440 map.UnmapUid(uid);
441 break;
443 case CHANGE_ADDED:
444 status = (*CommitData)(env, dbId, StateIndex, RecordId,
445 osync_change_get_data(change), true, errmsg);
446 if( !status ) {
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());
449 map.UnmapUid(uid);
450 return false;
452 cache[RecordId] = false;
453 break;
455 case CHANGE_MODIFIED:
456 status = (*CommitData)(env, dbId, StateIndex, RecordId,
457 osync_change_get_data(change), false, errmsg);
458 if( !status ) {
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());
461 map.UnmapUid(uid);
462 return false;
464 break;
466 default:
467 trace.log("Unknown change type");
468 osync_debug("barry-sync", 0, "Unknown change type");
469 break;
472 // Answer the call
473 osync_context_report_success(ctx);
474 return true;
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
486 return false;
490 static void sync_done(OSyncContext *ctx)
493 // This function will only be called if the sync was successfull
496 Trace trace("sync_done");
498 try {
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.
505 env->Reconnect();
507 // do cleanup for each database
508 if( FinishSync(ctx, env, &env->m_CalendarSync) &&
509 FinishSync(ctx, env, &env->m_ContactsSync) )
511 // Success
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);
527 env->Disconnect();
529 // Done!
530 osync_context_report_success(ctx);
533 static void finalize(void *data)
535 Trace trace("finalize");
537 BarryEnvironment *env = (BarryEnvironment *)data;
538 delete env;
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",
576 commit_change);
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",
582 commit_change);