Still need the exceptions flag....
[opensync/akonadi-sync-cdf.git] / src / datasink.cpp
blob6a5c5232b6f2009b57079be772d20e3f12b91c46
1 /*
2 Copyright (c) 2008 Volker Krause <vkrause@kde.org>
3 Copyright (c) 2010 Emanoil Kotsev <deloptes@yahoo.com>
5 $Id$
7 This library is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Library General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or (at your
10 option) any later version.
12 This library is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15 License for more details.
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301, USA.
23 #include "datasink.h"
25 #include <akonadi/collectionfetchjob.h>
26 #include <akonadi/collectionfetchscope.h>
27 #include <akonadi/itemdeletejob.h>
28 #include <akonadi/itemmodifyjob.h>
29 #include <akonadi/itemcreatejob.h>
30 #include <akonadi/itemfetchscope.h>
31 #include <akonadi/mimetypechecker.h>
34 // calendar includes
35 #include <kcal/incidence.h>
36 #include <kcal/icalformat.h>
38 // contact includes
39 #include <kabc/addressee.h>
40 #include <kabc/vcardconverter.h>
41 #include <kabc/vcardparser.h>
42 #include <kabc/vcardformat.h>
44 // TODO
45 // notes, todos, journals & includes are done by icalformat
46 // I am not quite sure about it, but I think
47 // this needs to be checked when working on todos and notes
49 #include <KDebug>
50 #include <KLocale>
52 using namespace Akonadi;
54 typedef boost::shared_ptr<KCal::Incidence> IncidencePtr;
56 DataSink::DataSink ( int type ) :
57 SinkBase ( GetChanges | Commit | SyncDone ),
58 m_Format("default"),
59 m_Url("default")
61 m_type = type;
64 DataSink::~DataSink()
66 kDebug() << "DataSink destructor called"; // TODO still needed
69 bool DataSink::initialize ( OSyncPlugin * plugin, OSyncPluginInfo * info, OSyncObjTypeSink *sink, OSyncError ** error )
71 m_Name = osync_objtype_sink_get_name ( sink );
72 kDebug() << "initializing" << m_Name;
73 Q_UNUSED ( plugin );
74 // Q_UNUSED ( info );
75 Q_UNUSED ( error );
77 // require configuration
78 OSyncPluginConfig *config = osync_plugin_info_get_config ( info );
79 if ( !config )
81 // osync_error_set ( error, OSYNC_ERROR_GENERIC, "Unable to get config." );
82 return false;
85 // require enabled ressource
86 OSyncPluginResource *resource = osync_plugin_config_find_active_resource ( config, osync_objtype_sink_get_name ( sink ) );
87 if ( ! resource || ! osync_plugin_resource_is_enabled(resource) )
88 return false;
90 // get url
91 m_Url = osync_plugin_resource_get_url ( resource );
93 // set format
94 OSyncList *objfrmtList = osync_plugin_resource_get_objformat_sinks ( resource );
95 const char *preferred = osync_plugin_resource_get_preferred_format(resource);
96 for ( OSyncList *r = objfrmtList;r;r = r->next )
98 OSyncObjFormatSink *objformatsink = ( OSyncObjFormatSink * ) r->data;
99 const char* tobjformat = osync_objformat_sink_get_objformat ( objformatsink );
101 // NOTE "application/x-vnd.kde.contactgroup" is used for contact groups ... we can use it probably later
103 // TODO how can I negotiate format ... is this here enough?
104 switch ( m_type )
106 case Contacts:
108 if ( !strcmp ( "vcard21", tobjformat ) )
109 m_Format = "vcard21";
110 // always prefer newer format
111 if ( !strcmp ( "vcard30", tobjformat ) )
112 m_Format = "vcard30";
113 m_MimeType = "text/directory";
114 break;
116 case Calendars:
118 if ( !strcmp ( "vevent10", tobjformat ) )
119 m_Format = "vevent10";
120 // always prefer newer format
121 if ( !strcmp ( "vevent20", tobjformat ) )
122 m_Format = "vevent20";
123 m_MimeType = "application/x-vnd.akonadi.calendar.event";
124 break;
126 case Notes:
128 if ( !strcmp ( "vnote11", tobjformat ) ) {
129 m_Format = "vnote11";
130 m_MimeType = "application/x-vnd.kde.notes";
132 if ( !strcmp ( "vjournal", tobjformat ) ) {
133 m_Format = "vjournal";
134 m_MimeType = "application/x-vnd.akonadi.calendar.journal";
136 break;
138 case Todos:
140 if ( !strcmp ( "vtodo10", tobjformat ) )
141 m_Format = "vtodo10";
142 // always prefer newer format
143 if ( !strcmp ( "vtodo20", tobjformat ) )
144 m_Format = "vtodo20";
145 m_MimeType = "application/x-vnd.akonadi.calendar.todo";
146 break;
149 default:
150 osync_list_free(objfrmtList);
151 return false;
155 osync_list_free(objfrmtList);
156 // this adds preffered to the resource configuration if not set
157 if ( ! preferred || strcmp(preferred,m_Format.toLatin1().data() ) )
158 osync_plugin_resource_set_preferred_format( resource, m_Format.toLatin1().data() );
160 kDebug() << "Has objformat: " << m_Format;
162 wrapSink ( sink );
163 // osync_objtype_sink_set_userdata ( sink, this );
165 osync_objtype_sink_enable_hashtable ( sink , true );
167 return true;
170 Akonadi::Collection DataSink::collection() const
172 kDebug();
174 const KUrl url = KUrl ( m_Url );
176 if ( url.isEmpty() )
178 error ( OSYNC_ERROR_MISCONFIGURATION, i18n ( "Url for object type \"%s\" is not configured.", m_type) );
179 return Collection();
182 return Collection::fromUrl ( url );
186 void DataSink::getChanges()
188 kDebug();
189 kDebug() << " DataSink::getChanges() called";
190 OSyncError *oerror = 0;
192 OSyncHashTable *hashtable = osync_objtype_sink_get_hashtable ( sink() );
194 if ( !hashtable ) {
195 kDebug() << "No hashtable";
196 error ( OSYNC_ERROR_FILE_NOT_FOUND, "No hashtable");
197 osync_trace ( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, osync_error_print ( &oerror ) );
198 return;
201 if ( getSlowSink() )
203 kDebug() << "we're in the middle of slow-syncing...";
204 osync_trace ( TRACE_INTERNAL, "resetting hashtable" );
205 if ( ! osync_hashtable_slowsync ( hashtable, &oerror ) )
207 osync_trace ( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, osync_error_print ( &oerror ) );
208 error ( OSYNC_ERROR_GENERIC, osync_error_print ( &oerror) );
209 osync_error_unref(&oerror);
210 return;
214 Akonadi::Collection col = collection() ;
216 // col.setContentMimeTypes( QStringList() << getMimeWithFormat(format) );
217 if ( !col.isValid() )
219 kDebug() << "No collection";
220 osync_trace ( TRACE_EXIT_ERROR, "%s: %s", __PRETTY_FUNCTION__, osync_error_print ( &oerror ) );
221 error ( OSYNC_ERROR_GENERIC, "No collection");
222 return;
225 ItemFetchJob *job = new ItemFetchJob ( col );
226 job->fetchScope().fetchFullPayload();
227 kDebug() << "Fetched full payload";
229 QObject::connect ( job, SIGNAL ( itemsReceived ( const Akonadi::Item::List & ) ), this, SLOT ( slotItemsReceived ( const Akonadi::Item::List & ) ) );
230 QObject::connect ( job, SIGNAL ( result ( KJob * ) ), this, SLOT ( slotGetChangesFinished ( KJob * ) ) );
232 if ( !job->exec() )
234 error ( OSYNC_ERROR_IO_ERROR, job->errorText() );
235 return;
240 void DataSink::slotItemsReceived ( const Item::List &items )
242 kDebug();
243 kDebug() << "retrieved" << items.count() << "items";
244 Akonadi::MimeTypeChecker checker;
245 checker.addWantedMimeType( m_MimeType );
247 Q_FOREACH ( const Item& item, items ) {
248 // report only items of given mimeType
249 if ( checker.isWantedItem( item ) )
250 reportChange ( item );
251 else
252 kDebug() << item.id() << item.mimeType() << "skipped!";
254 kDebug() << "slotItemsReceived done";
257 void DataSink::reportChange ( const Item& item )
259 kDebug() << ">>>>>>>>>>>>>>>>>>>";
260 kDebug() << "Id:" << item.id() << "\n";
261 kDebug() << "RemoteId:" << item.remoteId() << "\n";
262 kDebug() << "Mime:" << item.mimeType() << "\n";
263 kDebug() << "Revision:" << item.revision() << "\n";
264 kDebug() << "StorageCollectionId:" << item.storageCollectionId() << "\n";
265 kDebug() << "Url:" << item.url() << "\n";
266 kDebug() << "mtime:" << item.modificationTime().toString(Qt::ISODate) << "\n";
267 // kDebug() << "mtime:" << (uint) item.modificationTime().toTime_t () << "\n";
269 if ( item.remoteId().isEmpty() )
271 error( OSYNC_ERROR_EXPECTED, "item remote identifier missing" );
272 return;
274 OSyncChange *change = 0;
275 QString hash = getHash( item.id(), item.revision() ) ;
277 kDebug() << hash;
278 kDebug() << "item.payloadData().data()" << "\n" << item.payloadData().data();
280 OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env ( pluginInfo() );
282 OSyncError *oerror = 0;
283 OSyncHashTable *hashtable = osync_objtype_sink_get_hashtable ( sink() );
285 change = osync_change_new ( &oerror );
286 if ( !change )
288 osync_change_unref ( change );
289 warning ( oerror );
290 return;
293 osync_change_set_uid ( change, item.remoteId().toLatin1().data() );
294 // osync_change_set_uid ( change, QString::number( item.id() ).toLatin1() );
295 osync_change_set_hash ( change, getHash( item.id(), item.revision() ).toLatin1().data() );
297 OSyncChangeType changetype = osync_hashtable_get_changetype(hashtable, change);
298 osync_change_set_changetype(change, changetype);
300 osync_hashtable_update_change ( hashtable, change );
302 if ( changetype == OSYNC_CHANGE_TYPE_UNMODIFIED ) {
303 kDebug()<< "skipped (unmodified)" << change;
304 osync_change_unref(change);
305 osync_error_unref(&oerror);
306 return;
308 // Now you can set the data for the object
310 OSyncObjFormat *format = osync_format_env_find_objformat ( formatenv, m_Format.toLatin1().data() );
311 int newDataSize = item.payloadData().size();
312 char newData[newDataSize];
313 memcpy(newData, item.payloadData().data(), newDataSize);
314 // OSyncData *odata = osync_data_new ( item.payloadData().data() , item.payloadData().size(), format, &error );
315 OSyncData *odata = osync_data_new ( newData, newDataSize, format, &oerror );
316 if ( !odata )
318 // osync_change_unref((OSyncChange*) change);
319 osync_change_unref(change);
320 warning(oerror);
321 return;
323 osync_error_unref(&oerror);
325 kDebug()<< "context" << context();
326 // do I need this here
327 // osync_data_set_objtype( odata, m_Name.toLatin1().data() );
328 osync_change_set_data ( change, odata );
329 kDebug()<< "data " << odata;
330 // not sure but it gets probably delete together with change below
331 // osync_data_unref ( (OSyncData *) odata );
332 // osync_hashtable_update_change ( hashtable, change ); //Do we need an update after setting data?
334 osync_context_report_change ( context(), change );
335 // osync_hashtable_update_change ( hashtable, change );
336 // kDebug()<< "change" << change;
337 osync_change_unref ( change );
339 kDebug()<< "<<<<<<<<<<<<<< change done";
343 void DataSink::slotGetChangesFinished ( KJob * )
345 kDebug();
346 OSyncError *oerror = 0;
348 OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env( pluginInfo() );
349 OSyncHashTable *hashtable = osync_objtype_sink_get_hashtable ( sink() );
350 OSyncList *u, *uids = osync_hashtable_get_deleted ( hashtable );
351 for ( u = uids; u; u = u->next )
353 QString uid ( ( char * ) u->data );
354 kDebug() << "going to delete with uid:" << uid;
356 OSyncChange *change = osync_change_new ( &oerror );
357 if ( !change )
359 osync_change_unref ( change );
360 warning ( oerror );
361 continue;
364 osync_change_set_uid ( change, uid.toLatin1().data() );
365 QString hash = osync_change_get_hash(change);
366 kDebug() << "hash:" << hash;
367 osync_change_set_changetype ( change, OSYNC_CHANGE_TYPE_DELETED );
369 OSyncObjFormat *format = osync_format_env_find_objformat( formatenv, m_Format.toLatin1().data() );
370 oerror = 0;
371 OSyncData *data = osync_data_new( NULL, 0, format, &oerror );
372 if ( !data ) {
373 osync_change_unref( change );
374 warning( oerror );
375 continue;
378 osync_data_set_objtype( data, m_Name.toLatin1().data() );
379 osync_change_set_data( change, data );
380 osync_data_unref((OSyncData *)data);
381 // osync_hashtable_update_change ( hashtable, change );
383 osync_context_report_change ( context(), change );
385 osync_hashtable_update_change ( hashtable, change );
387 osync_change_unref ( change );
389 osync_list_free ( uids );
390 osync_error_unref(&oerror);
392 kDebug() << "got all changes success().";
393 success();
396 void DataSink::commit ( OSyncChange *change )
398 kDebug();
400 OSyncHashTable *hashtable = osync_objtype_sink_get_hashtable ( sink() );
402 QString remoteId = QString::fromLatin1 ( osync_change_get_uid ( change ) );
403 QString hash = QString::fromLatin1 ( osync_change_get_hash ( change ) );
405 //TODO: use id to identify items
406 // int id = idFromHash(hash);
407 // kDebug() << "change id:" << id;
408 kDebug() << "change uid :" << remoteId;
409 kDebug() << "change hash:" << hash;
410 kDebug() << "objform:" << osync_objformat_get_name ( osync_change_get_objformat ( change ) );
413 Akonadi::Collection col = collection();
415 if ( !col.isValid() ) {
416 error( OSYNC_ERROR_GENERIC, "Invalid collection.");
417 return;
420 switch ( (OSyncChangeType) osync_change_get_changetype ( change ) )
422 case OSYNC_CHANGE_TYPE_ADDED:
424 char *plain = 0; // plain is freed by data
425 osync_data_get_data ( osync_change_get_data ( change ), &plain, /*size*/0 );
426 QString str = QString::fromUtf8( plain );
427 // QString str = QString::fromLatin1( plain );
428 // QString str = QString::fromLocal8Bit( plain );
429 kDebug() << "data: " << str;
431 Item item;
432 setPayload ( &item, str );
433 // item.setId((qint64) remoteId.toLongLong());
434 item.setRemoteId( remoteId );
436 ItemCreateJob *job = new Akonadi::ItemCreateJob ( item, col );
437 if ( ! job->exec() ) {
438 error( OSYNC_ERROR_GENERIC, "Unable to create job for item.");
439 return;
440 } else {
441 item = job->item(); // handle !job->exec in return too..
442 if ( ! item.isValid() ) {
443 error( OSYNC_ERROR_GENERIC, "Unable to fetch item.");
444 return;
446 //TODO: Test
447 // kDebug() << "change qint:" << remoteId.toLongLong();
448 // item.setId((qint64) remoteId.toLongLong());
449 osync_change_set_uid ( change, item.remoteId().toLatin1().data() );
450 osync_change_set_hash ( change, getHash( item.id(), item.revision() ).toLatin1().data() );
452 break;
455 case OSYNC_CHANGE_TYPE_MODIFIED:
457 char *plain = 0; // plain is freed by data
458 osync_data_get_data ( osync_change_get_data ( change ), &plain, /*size*/0 );
459 QString str = QString::fromUtf8( plain );
461 Item item = fetchItem ( remoteId );
463 if ( ! item.isValid() ) {
464 error( OSYNC_ERROR_GENERIC, "Unable to fetch item.");
465 return;
467 setPayload ( &item, str );
468 kDebug() << "data" << str;
470 ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob ( item );
471 if ( ! modifyJob->exec() ) {
472 error ( OSYNC_ERROR_GENERIC, "Unable to run modify job.");
473 return;
474 } else {
475 item = modifyJob->item();
476 if ( ! item.isValid() ) {
477 error( OSYNC_ERROR_GENERIC, "Unable to modify item.");
478 return;
480 // ### Do Ineed this also here?
481 // kDebug() << "change qint:" << remoteId.toLongLong();
482 // item.setId((qint64) remoteId.toLongLong());
483 osync_change_set_uid ( change, item.remoteId().toLatin1().data() );
484 osync_change_set_hash ( change, getHash( item.id(), item.revision() ).toLatin1().data() );
487 break;
490 case OSYNC_CHANGE_TYPE_DELETED:
492 Item item = fetchItem ( remoteId );
493 if ( ! item.isValid() ) {
494 // FIXME break or return?
495 // error( OSYNC_ERROR_GENERIC, "Unable to fetch item");
496 break;
499 // kDebug() << "deleted: (testing skipped)" << remoteId;
500 // comment out to skip deleting for testing
501 ItemDeleteJob *job = new ItemDeleteJob( item );
502 if ( ! job->exec() ) {
503 error( OSYNC_ERROR_GENERIC, "Unable to delete item");
504 return;
506 osync_change_set_uid ( change, item.remoteId().toLatin1().data() );
507 break;
510 case OSYNC_CHANGE_TYPE_UNMODIFIED:
512 kDebug() << "UNMODIFIED";
513 // should we do something here?
514 break;
516 default:
517 kDebug() << "got invalid changetype?";
518 error(OSYNC_ERROR_GENERIC, "got invalid changetype");
519 return;
522 osync_hashtable_update_change ( hashtable, change );
524 success();
527 bool DataSink::setPayload ( Item *item, const QString &str )
529 kDebug();
530 item->setMimeType ( m_MimeType );
531 kDebug()<< "To mimetype: " << m_MimeType;
532 switch ( m_type )
534 case Contacts:
536 kDebug() << "type = contacts";
537 KABC::VCardConverter converter;
538 KABC::Addressee vcard = converter.parseVCard ( str.toUtf8() );
539 item->setPayload<KABC::Addressee> ( vcard );
540 kDebug() << "payload: " << vcard.toString().toUtf8();
541 break;
543 case Calendars:
545 kDebug() << "type = events";
546 KCal::ICalFormat format;
547 KCal::Incidence *calEntry = format.fromString ( str.toUtf8() );
548 item->setPayload<IncidencePtr> ( IncidencePtr ( calEntry->clone() ) );
549 kDebug() << "payload: " << str.toUtf8();
550 break;
552 case Todos:
554 kDebug() << "type = todos";
555 KCal::ICalFormat format;
556 KCal::Incidence *todoEntry = format.fromString ( str.toUtf8() );
557 item->setPayload<IncidencePtr> ( IncidencePtr ( todoEntry->clone() ) );
558 kDebug() << "payload: " << str.toUtf8();
559 break;
561 case Notes:
563 kDebug() << "type = notes";
564 KCal::ICalFormat format;
565 KCal::Incidence *noteEntry = format.fromString ( str.toUtf8() );
566 item->setPayload<IncidencePtr> ( IncidencePtr ( noteEntry->clone() ) );
567 kDebug() << "payload: " << str.toUtf8();
568 break;
570 default:
571 // should not happen
572 return false;
575 return true;
578 const Item DataSink::fetchItem ( int id )
580 kDebug();
581 ItemFetchJob *fetchJob = new ItemFetchJob( Item( id ) );
582 fetchJob->fetchScope().fetchFullPayload();
584 if( fetchJob->exec() ) {
585 foreach ( const Item &item, fetchJob->items() ) {
586 if( item.id() == id ) {
587 kDebug() << "got item";
588 return item;
593 // no such item found?
594 // we'll check after calling this function
595 return Item();
598 const Item DataSink::fetchItem ( const QString& remoteId )
600 kDebug();
602 ItemFetchJob *fetchJob = new ItemFetchJob ( collection() );
603 fetchJob->fetchScope().fetchFullPayload();
604 if ( fetchJob->exec() )
605 foreach ( const Item &item, fetchJob->items() )
606 if ( item.remoteId() == remoteId )
607 return item;
608 // no such item found?
609 // we'll check after calling this function
610 return Item();
613 void DataSink::syncDone()
615 kDebug() << "sync for sink member done";
616 // Do we need this in 0.40???
617 // OSyncError *error = 0;
618 // osync_objtype_sink_save_hashtable ( sink() , &error );
619 // if ( error ) {
620 // warning ( error );
621 // return;
622 // }
623 success();
626 QString DataSink::getHash( int id, int rev ) {
627 return QString::number(id) + "-" + QString::number( rev ) ;
630 int DataSink::idFromHash( const QString hash) {
631 QString str = hash;
632 str.remove(QRegExp("-.*"));
633 kDebug() << str;
634 return str.toInt();
636 #include "datasink.moc"