2 Copyright (c) 2008 Volker Krause <vkrause@kde.org>
3 Copyright (c) 2010 Emanoil Kotsev <deloptes@yahoo.com>
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
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>
35 #include <kcal/incidence.h>
36 #include <kcal/icalformat.h>
39 #include <kabc/addressee.h>
40 #include <kabc/vcardconverter.h>
41 #include <kabc/vcardparser.h>
42 #include <kabc/vcardformat.h>
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
52 using namespace Akonadi
;
54 typedef boost::shared_ptr
<KCal::Incidence
> IncidencePtr
;
56 DataSink::DataSink ( int type
) :
57 SinkBase ( GetChanges
| Commit
| SyncDone
),
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
;
77 // require configuration
78 OSyncPluginConfig
*config
= osync_plugin_info_get_config ( info
);
81 // osync_error_set ( error, OSYNC_ERROR_GENERIC, "Unable to get config." );
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
) )
91 m_Url
= osync_plugin_resource_get_url ( resource
);
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?
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";
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";
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";
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";
150 osync_list_free(objfrmtList
);
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
;
163 // osync_objtype_sink_set_userdata ( sink, this );
165 osync_objtype_sink_enable_hashtable ( sink
, true );
170 Akonadi::Collection
DataSink::collection() const
174 const KUrl url
= KUrl ( m_Url
);
178 error ( OSYNC_ERROR_MISCONFIGURATION
, i18n ( "Url for object type \"%s\" is not configured.", m_type
) );
182 return Collection::fromUrl ( url
);
186 void DataSink::getChanges()
189 kDebug() << " DataSink::getChanges() called";
190 OSyncError
*oerror
= 0;
192 OSyncHashTable
*hashtable
= osync_objtype_sink_get_hashtable ( sink() );
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
) );
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
);
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");
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
* ) ) );
234 error ( OSYNC_ERROR_IO_ERROR
, job
->errorText() );
240 void DataSink::slotItemsReceived ( const Item::List
&items
)
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
);
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" );
274 OSyncChange
*change
= 0;
275 QString hash
= getHash( item
.id(), item
.revision() ) ;
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
);
288 osync_change_unref ( change
);
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
);
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
);
318 // osync_change_unref((OSyncChange*) change);
319 osync_change_unref(change
);
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
* )
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
);
359 osync_change_unref ( change
);
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() );
371 OSyncData
*data
= osync_data_new( NULL
, 0, format
, &oerror
);
373 osync_change_unref( change
);
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().";
396 void DataSink::commit ( OSyncChange
*change
)
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.");
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
;
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.");
441 item
= job
->item(); // handle !job->exec in return too..
442 if ( ! item
.isValid() ) {
443 error( OSYNC_ERROR_GENERIC
, "Unable to fetch item.");
446 kDebug() << "change qint:" << remoteId
.toLongLong();
447 item
.setId((qint64
) remoteId
.toLongLong());
448 osync_change_set_uid ( change
, item
.remoteId().toLatin1().data() );
449 osync_change_set_hash ( change
, getHash( item
.id(), item
.revision() ).toLatin1().data() );
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.");
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.");
475 item
= modifyJob
->item();
476 if ( ! item
.isValid() ) {
477 error( OSYNC_ERROR_GENERIC
, "Unable to modify item.");
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() );
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");
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");
506 osync_change_set_uid ( change
, item
.remoteId().toLatin1().data() );
510 case OSYNC_CHANGE_TYPE_UNMODIFIED
:
512 kDebug() << "UNMODIFIED";
513 // should we do something here?
517 kDebug() << "got invalid changetype?";
518 error(OSYNC_ERROR_GENERIC
, "got invalid changetype");
522 osync_hashtable_update_change ( hashtable
, change
);
527 bool DataSink::setPayload ( Item
*item
, const QString
&str
)
530 item
->setMimeType ( m_MimeType
);
531 kDebug()<< "To mimetype: " << m_MimeType
;
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();
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();
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();
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();
578 const Item
DataSink::fetchItem ( int id
)
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";
593 // no such item found?
594 // we'll check after calling this function
598 const Item
DataSink::fetchItem ( const QString
& remoteId
)
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
)
608 // no such item found?
609 // we'll check after calling this function
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 );
620 // warning ( error );
626 QString
DataSink::getHash( int id
, int rev
) {
627 return QString::number(id
) + "-" + QString::number( rev
) ;
630 int DataSink::idFromHash( const QString hash
) {
632 str
.remove(QRegExp("-.*"));
636 #include "datasink.moc"