28 #include <QScriptValue>
29 #include <QScriptEngine>
30 #include <QTemporaryFile>
31 #include <QNetworkAccessManager>
54 GeoDataPlacemark m_placemarkA;
55 GeoDataPlacemark m_placemarkB;
58 class BookmarkSyncManager::Private
64 CloudSyncManager *m_cloudSyncManager;
66 QNetworkAccessManager m_network;
67 QString m_uploadEndpoint;
68 QString m_downloadEndpoint;
69 QString m_timestampEndpoint;
71 QNetworkReply* m_uploadReply;
72 QNetworkReply* m_downloadReply;
73 QNetworkReply* m_timestampReply;
75 QString m_cloudTimestamp;
78 QString m_localBookmarksPath;
79 QString m_bookmarksTimestamp;
81 QList<DiffItem> m_diffA;
82 QList<DiffItem> m_diffB;
83 QList<DiffItem> m_merged;
84 DiffItem m_conflictItem;
86 BookmarkManager* m_bookmarkManager;
88 bool m_bookmarkSyncEnabled;
95 QUrl endpointUrl(
const QString &endpoint );
100 void uploadBookmarks();
105 void downloadBookmarks();
110 void downloadTimestamp();
116 bool cloudBookmarksModified(
const QString &cloudTimestamp );
128 QString lastSyncedKmlPath();
137 QList<DiffItem> getPlacemarks(GeoDataDocument *document, GeoDataDocument *other,
DiffItem::Status diffDirection );
147 QList<DiffItem> getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other,
DiffItem::Status diffDirection );
155 GeoDataPlacemark* findPlacemark( GeoDataContainer* container,
const GeoDataPlacemark &bookmark )
const;
163 void determineDiffStatus( DiffItem &item, GeoDataDocument* document );
171 QList<DiffItem> diff( QString &sourcePath, QString &destinationPath );
172 QList<DiffItem> diff( QString &sourcePath, QIODevice* destination );
173 QList<DiffItem> diff( QIODevice* source, QString &destinationPath );
174 QList<DiffItem> diff( QIODevice *source, QIODevice* destination );
190 GeoDataFolder* createFolders( GeoDataContainer *container, QStringList &pathList );
197 GeoDataDocument* constructDocument(
const QList<DiffItem> &mergedList );
199 void saveDownloadedToCache(
const QByteArray &kml );
201 void parseTimestamp();
202 void copyLocalToCache();
204 void continueSynchronization();
205 void completeSynchronization();
206 void completeMerge();
207 void completeUpload();
210 BookmarkSyncManager::Private::Private(BookmarkSyncManager *parent, CloudSyncManager *cloudSyncManager ) :
212 m_cloudSyncManager( cloudSyncManager ),
213 m_bookmarkManager( 0 ),
214 m_bookmarkSyncEnabled( false )
216 m_cachePath = QString(
"%0/cloudsync/cache/bookmarks" ).arg( MarbleDirs::localPath() );
217 m_localBookmarksPath = QString(
"%0/bookmarks/bookmarks.kml" ).arg( MarbleDirs::localPath() );
218 m_downloadEndpoint =
"bookmarks/kml";
219 m_uploadEndpoint =
"bookmarks/update";
220 m_timestampEndpoint =
"bookmarks/timestamp";
225 d( new Private( this, cloudSyncManager ) )
227 d->m_syncTimer.setInterval( 60 * 60 * 1000 );
239 return d->m_bookmarkSyncEnabled;
245 d->m_bookmarkSyncEnabled = enabled;
256 d->m_bookmarkManager = manager;
268 d->m_syncTimer.start();
269 d->downloadTimestamp();
272 QUrl BookmarkSyncManager::Private::endpointUrl(
const QString &endpoint )
274 return QUrl( QString(
"%0/%1" ).arg( m_cloudSyncManager->apiUrl().toString() ).arg( endpoint ) );
277 void BookmarkSyncManager::Private::uploadBookmarks()
280 QByteArray lineBreak =
"\r\n";
281 QString word =
"----MarbleCloudBoundary";
282 QString boundary = QString(
"--%0" ).arg( word );
283 QNetworkRequest request( endpointUrl( m_uploadEndpoint ) );
284 request.setHeader( QNetworkRequest::ContentTypeHeader, QString(
"multipart/form-data; boundary=%0" ).arg( word ) );
286 data.append( QString( boundary + lineBreak ).toUtf8() );
287 data.append(
"Content-Disposition: form-data; name=\"bookmarks\"; filename=\"bookmarks.kml\"" + lineBreak );
288 data.append(
"Content-Type: application/vnd.google-earth.kml+xml" + lineBreak + lineBreak );
290 QFile bookmarksFile( m_localBookmarksPath );
291 if( !bookmarksFile.open( QFile::ReadOnly ) ) {
292 mDebug() <<
"Failed to open file" << bookmarksFile.fileName()
293 <<
". It is either missing or not readable.";
297 QByteArray kmlContent = bookmarksFile.readAll();
298 data.append( kmlContent + lineBreak + lineBreak );
299 data.append( QString( boundary ).toUtf8() );
300 bookmarksFile.close();
302 m_uploadReply = m_network.post( request, data );
303 connect( m_uploadReply, SIGNAL(uploadProgress(qint64,qint64)),
304 m_q, SIGNAL(uploadProgress(qint64,qint64)) );
305 connect( m_uploadReply, SIGNAL(finished()),
306 m_q, SLOT(completeUpload()) );
309 void BookmarkSyncManager::Private::downloadBookmarks()
311 QNetworkRequest request( endpointUrl( m_downloadEndpoint ) );
312 m_downloadReply = m_network.get( request );
313 connect( m_downloadReply, SIGNAL(finished()),
314 m_q, SLOT(completeSynchronization()) );
315 connect( m_downloadReply, SIGNAL(downloadProgress(qint64,qint64)),
316 m_q, SIGNAL(downloadProgress(qint64,qint64)) );
319 void BookmarkSyncManager::Private::downloadTimestamp()
321 mDebug() <<
"Determining remote bookmark state.";
322 m_timestampReply = m_network.get( QNetworkRequest( endpointUrl( m_timestampEndpoint ) ) );
323 connect( m_timestampReply, SIGNAL(finished()),
324 m_q, SLOT(parseTimestamp()) );
327 bool BookmarkSyncManager::Private::cloudBookmarksModified(
const QString &cloudTimestamp )
329 QStringList entryList = QDir( m_cachePath ).entryList(
332 QStringList() <<
"*.kml",
333 QDir::NoFilter, QDir::Name );
334 if( !entryList.isEmpty() ) {
335 QString lastSynced = entryList.last();
336 lastSynced.chop( 4 );
337 return cloudTimestamp != lastSynced;
343 void BookmarkSyncManager::Private::clearCache()
345 QDir cacheDir( m_cachePath );
346 QFileInfoList fileInfoList = cacheDir.entryInfoList(
347 QStringList() <<
"*.kml",
348 QDir::NoFilter, QDir::Name );
349 if( !fileInfoList.isEmpty() ) {
350 foreach ( QFileInfo fileInfo, fileInfoList ) {
351 QFile file( fileInfo.absoluteFilePath() );
352 bool removed = file.remove();
354 mDebug() <<
"Could not delete" << file.fileName() <<
355 "Make sure you have sufficient permissions.";
361 QString BookmarkSyncManager::Private::lastSyncedKmlPath()
363 QDir cacheDir( m_cachePath );
364 QFileInfoList fileInfoList = cacheDir.entryInfoList(
365 QStringList() <<
"*.kml",
366 QDir::NoFilter, QDir::Name );
367 if( !fileInfoList.isEmpty() ) {
368 return fileInfoList.last().absoluteFilePath();
374 QList<DiffItem> BookmarkSyncManager::Private::getPlacemarks( GeoDataDocument *document, GeoDataDocument *other,
DiffItem::Status diffDirection )
376 QList<DiffItem> diffItems;
377 foreach ( GeoDataFolder *folder, document->folderList() ) {
378 QString path = QString(
"/%0" ).arg( folder->name() );
379 diffItems.append( getPlacemarks( folder, path, other, diffDirection ) );
385 QList<DiffItem> BookmarkSyncManager::Private::getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other,
DiffItem::Status diffDirection )
387 QList<DiffItem> diffItems;
388 foreach ( GeoDataFolder *folder, folder->folderList() ) {
389 QString newPath = QString(
"%0/%1" ).arg( path, folder->name() );
390 diffItems.append( getPlacemarks( folder, newPath, other, diffDirection ) );
393 foreach( GeoDataPlacemark *placemark, folder->placemarkList() ) {
395 diffItem.m_path = path;
396 diffItem.m_placemarkA = *placemark;
397 switch ( diffDirection ) {
398 case DiffItem::Source:
399 diffItem.m_origin = DiffItem::Destination;
401 case DiffItem::Destination:
402 diffItem.m_origin = DiffItem::Source;
408 determineDiffStatus( diffItem, other );
410 if( !( diffItem.m_action == DiffItem::NoAction && diffItem.m_origin == DiffItem::Destination )
411 && !( diffItem.m_action == DiffItem::Changed && diffItem.m_origin == DiffItem::Source ) ) {
412 diffItems.append( diffItem );
419 GeoDataPlacemark* BookmarkSyncManager::Private::findPlacemark( GeoDataContainer* container,
const GeoDataPlacemark &bookmark )
const
421 foreach( GeoDataPlacemark* placemark, container->placemarkList() ) {
427 foreach( GeoDataFolder* folder, container->folderList() ) {
428 GeoDataPlacemark* placemark = findPlacemark( folder, bookmark );
437 void BookmarkSyncManager::Private::determineDiffStatus( DiffItem &item, GeoDataDocument *document )
439 GeoDataPlacemark *match = findPlacemark( document, item.m_placemarkA );
442 item.m_placemarkB = *match;
443 bool nameChanged = item.m_placemarkA.name() != item.m_placemarkB.name();
444 bool descChanged = item.m_placemarkA.description() != item.m_placemarkB.description();
445 bool lookAtChanged = item.m_placemarkA.lookAt()->latitude() != item.m_placemarkB.lookAt()->latitude() ||
446 item.m_placemarkA.lookAt()->longitude() != item.m_placemarkB.lookAt()->longitude() ||
447 item.m_placemarkA.lookAt()->altitude() != item.m_placemarkB.lookAt()->altitude() ||
448 item.m_placemarkA.lookAt()->range() != item.m_placemarkB.lookAt()->range();
449 if( nameChanged || descChanged || lookAtChanged ) {
450 item.m_action = DiffItem::Changed;
452 item.m_action = DiffItem::NoAction;
455 switch( item.m_origin ) {
456 case DiffItem::Source:
457 item.m_action = DiffItem::Deleted;
458 item.m_placemarkB = item.m_placemarkA;
460 case DiffItem::Destination:
461 item.m_action = DiffItem::Created;
468 QList<DiffItem> BookmarkSyncManager::Private::diff( QString &sourcePath, QString &destinationPath )
470 QFile fileB( destinationPath );
471 if( !fileB.open( QFile::ReadOnly ) ) {
472 mDebug() <<
"Could not open file " << fileB.fileName();
474 return diff( sourcePath, &fileB );
477 QList<DiffItem> BookmarkSyncManager::Private::diff( QString &sourcePath, QIODevice *fileB )
479 QFile fileA( sourcePath );
480 if( !fileA.open( QFile::ReadOnly ) ) {
481 mDebug() <<
"Could not open file " << fileA.fileName();
484 return diff( &fileA, fileB );
487 QList<DiffItem> BookmarkSyncManager::Private::diff( QIODevice *source, QString &destinationPath )
489 QFile fileB( destinationPath );
490 if( !fileB.open( QFile::ReadOnly ) ) {
491 mDebug() <<
"Could not open file " << fileB.fileName();
494 return diff( source, &fileB );
497 QList<DiffItem> BookmarkSyncManager::Private::diff( QIODevice *fileA, QIODevice *fileB )
500 parserA.read( fileA );
501 GeoDataDocument *documentA =
dynamic_cast<GeoDataDocument*
>( parserA.releaseDocument() );
504 parserB.read( fileB );
505 GeoDataDocument *documentB =
dynamic_cast<GeoDataDocument*
>( parserB.releaseDocument() );
507 QList<DiffItem> diffItems = getPlacemarks( documentA, documentB, DiffItem::Destination );
508 diffItems.append( getPlacemarks( documentB, documentA, DiffItem::Source ) );
511 for(
int i = 0; i < diffItems.count(); i++ ) {
512 for(
int p = i + 1; p < diffItems.count(); p++ ) {
513 if( ( diffItems[i].m_origin == DiffItem::Source )
514 && ( diffItems[i].m_action == DiffItem::NoAction )
517 && ( diffItems[i].m_path != diffItems[p].m_path ) ) {
518 diffItems[p].m_action = DiffItem::Changed;
526 void BookmarkSyncManager::Private::merge()
528 foreach( DiffItem itemA, m_diffA ) {
529 if( itemA.m_action == DiffItem::NoAction ) {
530 bool deleted =
false;
531 bool changed =
false;
534 foreach( DiffItem itemB, m_diffB ) {
536 if( itemB.m_action == DiffItem::Deleted ) {
538 }
else if( itemB.m_action == DiffItem::Changed ) {
545 m_merged.append( other );
546 }
else if( !deleted ) {
547 m_merged.append( itemA );
549 }
else if( itemA.m_action == DiffItem::Created ) {
550 m_merged.append( itemA );
551 }
else if( itemA.m_action == DiffItem::Changed || itemA.m_action == DiffItem::Deleted ) {
552 bool conflict =
false;
555 foreach( DiffItem itemB, m_diffB ) {
557 if( ( itemA.m_action == DiffItem::Changed && ( itemB.m_action == DiffItem::Changed || itemB.m_action == DiffItem::Deleted ) )
558 || ( itemA.m_action == DiffItem::Deleted && itemB.m_action == DiffItem::Changed ) ) {
565 if( !conflict && itemA.m_action == DiffItem::Changed ) {
566 m_merged.append( itemA );
567 }
else if ( conflict ) {
568 m_conflictItem = other;
569 MergeItem *mergeItem =
new MergeItem();
570 mergeItem->setPathA( itemA.m_path );
571 mergeItem->setPathB( other.m_path );
572 mergeItem->setPlacemarkA( itemA.m_placemarkA );
573 mergeItem->setPlacemarkB( other.m_placemarkA );
575 switch( itemA.m_action ) {
576 case DiffItem::Changed:
579 case DiffItem::Deleted:
586 switch( other.m_action ) {
587 case DiffItem::Changed:
590 case DiffItem::Deleted:
597 emit m_q->mergeConflict( mergeItem );
602 if( !m_diffA.isEmpty() ) {
603 m_diffA.removeFirst();
607 foreach( DiffItem itemB, m_diffB ) {
608 if( itemB.m_action == DiffItem::Created ) {
609 m_merged.append( itemB );
616 GeoDataFolder* BookmarkSyncManager::Private::createFolders( GeoDataContainer *container, QStringList &pathList )
618 GeoDataFolder *folder = 0;
619 if( pathList.count() > 0 ) {
620 QString name = pathList.takeFirst();
622 foreach( GeoDataFolder *otherFolder, container->folderList() ) {
623 if( otherFolder->name() == name ) {
624 folder = otherFolder;
629 folder =
new GeoDataFolder();
630 folder->setName( name );
631 container->append( folder );
634 if( pathList.count() == 0 ) {
639 return createFolders( folder, pathList );
642 GeoDataDocument* BookmarkSyncManager::Private::constructDocument(
const QList<DiffItem> &mergedList )
644 GeoDataDocument *document =
new GeoDataDocument();
645 document->setName( tr(
"Bookmarks" ) );
647 foreach( DiffItem item, mergedList ) {
648 GeoDataPlacemark *placemark =
new GeoDataPlacemark( item.m_placemarkA );
649 QStringList splitted = item.m_path.split(
"/", QString::SkipEmptyParts );
650 GeoDataFolder *folder = createFolders( document, splitted );
651 folder->append( placemark );
663 if( !d->m_diffA.isEmpty() ) {
664 diffItem = d->m_diffA.first();
668 diffItem = d->m_conflictItem;
674 if( diffItem.m_action != DiffItem::Deleted ) {
675 d->m_merged.append( diffItem );
678 if( !d->m_diffA.isEmpty() ) {
679 d->m_diffA.removeFirst();
685 void BookmarkSyncManager::Private::saveDownloadedToCache(
const QByteArray &kml )
687 QString localBookmarksDir = m_localBookmarksPath;
688 QDir().mkdir( localBookmarksDir.remove(
"bookmarks.kml" ) );
689 QFile bookmarksFile( m_localBookmarksPath );
690 if( !bookmarksFile.open( QFile::ReadWrite ) ) {
691 mDebug() <<
"Failed to open file" << bookmarksFile.fileName()
692 <<
". It is either missing or not readable.";
696 bookmarksFile.write( kml );
697 bookmarksFile.close();
701 void BookmarkSyncManager::Private::parseTimestamp()
703 QString response = m_timestampReply->readAll();
704 QScriptEngine engine;
705 QScriptValue parsedResponse = engine.evaluate( QString(
"(%0)" ).arg( response ) );
706 QString timestamp = parsedResponse.property(
"data" ).toString();
707 m_cloudTimestamp = timestamp;
708 mDebug() <<
"Remote bookmark timestamp is " << m_cloudTimestamp;
709 continueSynchronization();
711 void BookmarkSyncManager::Private::copyLocalToCache()
713 QDir().mkpath( m_cachePath );
716 QFile bookmarksFile( m_localBookmarksPath );
717 bookmarksFile.copy( QString(
"%0/%1.kml" ).arg( m_cachePath, m_cloudTimestamp ) );
719 disconnect( m_bookmarkManager, SIGNAL(bookmarksChanged()), m_q, SLOT(startBookmarkSync()) );
720 m_bookmarkManager->loadFile(
"bookmarks/bookmarks.kml" );
721 connect( m_bookmarkManager, SIGNAL(bookmarksChanged()), m_q, SLOT(startBookmarkSync()) );
722 emit m_q->syncComplete();
726 void BookmarkSyncManager::Private::continueSynchronization()
728 bool cloudModified = cloudBookmarksModified( m_cloudTimestamp );
729 if( cloudModified ) {
732 QString lastSyncedPath = lastSyncedKmlPath();
733 if( lastSyncedPath == QString() ) {
734 mDebug() <<
"Never synced. Uploading bookmarks.";
737 QList<DiffItem> diffList = diff( lastSyncedPath, m_localBookmarksPath );
738 bool localModified =
false;
739 foreach( DiffItem item, diffList ) {
740 if( item.m_action != DiffItem::NoAction ) {
741 localModified =
true;
745 if( localModified ) {
746 mDebug() <<
"Local modifications, uploading.";
753 void BookmarkSyncManager::Private::completeSynchronization()
755 mDebug() <<
"Merging remote and local bookmark file";
756 QString lastSyncedPath = lastSyncedKmlPath();
757 QFile localBookmarksFile( m_localBookmarksPath );
758 QByteArray result = m_downloadReply->readAll();
759 QBuffer buffer( &result );
760 buffer.open( QIODevice::ReadOnly );
762 if( lastSyncedPath == QString() ) {
763 if( localBookmarksFile.exists() ) {
764 mDebug() <<
"Conflict between remote bookmarks and local ones";
765 m_diffA = diff( &buffer, m_localBookmarksPath );
766 m_diffB = diff( m_localBookmarksPath, &buffer );
768 saveDownloadedToCache( result );
774 m_diffA = diff( lastSyncedPath, m_localBookmarksPath );
775 m_diffB = diff( lastSyncedPath, &buffer );
782 void BookmarkSyncManager::Private::completeMerge()
784 QFile localBookmarksFile( m_localBookmarksPath );
785 GeoDataDocument *doc = constructDocument( m_merged );
787 localBookmarksFile.remove();
788 localBookmarksFile.open( QFile::ReadWrite );
789 writer.write( &localBookmarksFile, doc );
790 localBookmarksFile.close();
794 void BookmarkSyncManager::Private::completeUpload()
796 QString response = m_uploadReply->readAll();
797 QScriptEngine engine;
798 QScriptValue parsedResponse = engine.evaluate( QString(
"(%0)" ).arg( response ) );
799 QString timestamp = parsedResponse.property(
"data" ).toString();
800 m_cloudTimestamp = timestamp;
801 mDebug() <<
"Uploaded bookmarks to remote server. Timestamp is " << m_cloudTimestamp;
807 #include "BookmarkSyncManager.moc"
void setBookmarkManager(BookmarkManager *manager)
MergeItem::Resolution resolution
bool isBookmarkSyncEnabled() const
Checks if the user enabled bookmark synchronization.
This file contains the headers for MarbleModel.
void setBookmarkSyncEnabled(bool enabled)
Setter for enabling/disabling bookmark synchronization.
qreal distanceSphere(qreal lon1, qreal lat1, qreal lon2, qreal lat2)
This method calculates the shortest distance between two points on a sphere.
void startBookmarkSync()
Initiates running of synchronization "method chain".
This class is responsible for loading the book mark objects from the files and various book mark oper...
BookmarkSyncManager(CloudSyncManager *cloudSyncManager)
void bookmarkSyncEnabledChanged(bool enabled)
void resolveConflict(MergeItem *item)
QDebug mDebug()
a function to replace qDebug() in Marble library code