- renamed barry-config to barry-sync, the default configuration
[barry.git] / opensync-plugin / src / barry_sync.cc
blob4beb8cb1920166aa3e30213336b9835959def4f2
1 //
2 // \file barry_sync.cc
3 // Opensync module for the USB Blackberry handheld
4 //
6 /*
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"
26 #include "vevent.h"
27 #include "trace.h"
28 #include <string>
29 #include <glib.h>
30 #include <stdint.h>
31 //#include <stdlib.h>
32 //#include <string.h>
33 //#include <assert.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 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,
58 GetData_t getdata)
60 Trace trace("GetChanges");
62 // shortcut references
63 using namespace Barry;
64 using Barry::RecordStateTable;
65 Controller &con = *env->m_pCon;
67 // fetch state table
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;
82 // search the cache
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);
90 else {
91 // in the cache... dirty?
92 if( state.Dirty ) {
93 // modified
94 trace.log("found a MODIFIED change");
95 change = osync_change_new();
96 osync_change_set_changetype(change, CHANGE_MODIFIED);
98 else {
99 trace.log("no change detected");
103 // finish filling out the change object
104 if( change ) {
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);
111 g_free(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 )
136 break; // found
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);
151 trace.log(uid);
152 g_free(uid);
154 // report the change
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.
163 // start fresh
164 cache.clear();
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 ) {
180 return 0;
181 // return &VCardConverter::CommitRecordData;
183 else {
184 return 0;
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;
198 else {
199 return 0;
204 bool CommitChange(BarryEnvironment *env,
205 unsigned int dbId,
206 OSyncContext *ctx,
207 OSyncChange *change)
209 Trace trace("CommitChange");
211 // find the needed commit function, based on objtype of the change
212 CommitData_t CommitData = GetCommitFunction(change);
213 if( !CommitData ) {
214 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
215 "unable to get commit function pointer");
216 return false;
219 BarryEnvironment::cache_type *pCache = GetCache(env, change);
220 if( !pCache ) {
221 osync_context_report_error(ctx, OSYNC_ERROR_GENERIC,
222 "unable to get cache");
223 return false;
226 try {
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);
240 return false;
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
246 // modifying
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",
252 RecordId);
253 return false;
257 std::string errmsg;
259 switch( osync_change_get_changetype(change) )
261 case CHANGE_DELETED:
262 con.DeleteRecord(dbId, StateIndex);
263 pCache->erase(RecordId);
264 break;
266 case CHANGE_ADDED:
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());
269 return false;
271 (*pCache)[RecordId] = false;
272 break;
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());
277 return false;
279 break;
281 default:
282 osync_debug("barry-sync", 0, "Unknown change type");
283 break;
286 // Answer the call
287 osync_context_report_success(ctx);
288 return true;
291 catch( std::exception &e ) {
292 osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what());
293 return false;
300 //////////////////////////////////////////////////////////////////////////////
302 // OpenSync API
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
312 try {
313 // FIXME - near the end of release, do a run with
314 // this set to true, and look for USB protocol
315 // inefficiencies.
316 Barry::Init(false);
318 env = new BarryEnvironment(member);
320 // Load config file for this plugin
321 char *configdata;
322 int configsize;
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));
326 delete env;
327 return NULL;
330 // Process the configdata here and set the options on your environment
331 env->ParseConfig(configdata, configsize);
332 free(configdata);
334 // Load all needed cache files
335 if( env->m_SyncCalendar ) {
336 env->LoadCalendarCache();
339 if( env->m_SyncContacts ) {
340 env->LoadContactsCache();
343 return env;
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());
349 delete env;
350 return NULL;
352 catch( std::exception &e ) {
353 osync_error_update(error, "%s", e.what());
354 delete env;
355 return NULL;
359 static void connect(OSyncContext *ctx)
361 Trace trace("connect");
363 try {
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
371 Barry::Probe probe;
372 int nIndex = probe.FindActive(env->m_pin);
373 if( nIndex == -1 ) {
374 osync_context_report_error(ctx, OSYNC_ERROR_NO_CONNECTION, "Unable to find PIN %lu", env->m_pin);
375 return;
377 env->m_ProbeResult = probe.Get(nIndex);
379 // Create controller
380 env->m_pCon = new Barry::Controller(env->m_ProbeResult);
381 env->m_pCon->OpenMode(Barry::Controller::Desktop);
383 // Success!
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,
394 "%s", e.what());
398 static void get_changeinfo(OSyncContext *ctx)
400 Trace trace("get_changeinfo");
402 try {
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);
419 // Success!
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);
438 try {
439 Barry::Controller &con = *env->m_pCon;
441 // fetch state table
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
453 // read/delete/read.
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);
460 return;
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);
469 return;
474 // get the state table again, so we can update
475 // the cache on success, later
476 con.GetRecordStateTable(dbId, env->m_CalendarTable);
478 // all done
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");
495 try {
497 BarryEnvironment *env = (BarryEnvironment *)osync_context_get_plugin_data(ctx);
499 if( env->m_SyncCalendar ) {
500 // update the cache
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 ) {
511 // update the cache
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();
521 // Success
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);
536 env->Disconnect();
538 // Done!
539 osync_context_report_success(ctx);
542 static void finalize(void *data)
544 Trace trace("finalize");
546 BarryEnvironment *env = (BarryEnvironment *)data;
547 delete env;
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);