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;
84 DiffItem m_conflictItem;
86 BookmarkManager* m_bookmarkManager;
88 bool m_bookmarkSyncEnabled;
100 void uploadBookmarks();
105 void downloadBookmarks();
110 void downloadTimestamp();
116 bool cloudBookmarksModified(
const QString &cloudTimestamp )
const;
128 QString lastSyncedKmlPath()
const;
155 const GeoDataPlacemark* findPlacemark( GeoDataContainer* container,
const GeoDataPlacemark &bookmark )
const;
163 void determineDiffStatus( DiffItem &item, GeoDataDocument* document )
const;
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 );
238 const QString last = d->lastSyncedKmlPath();
246 return d->m_bookmarkSyncEnabled;
252 d->m_bookmarkSyncEnabled = enabled;
263 d->m_bookmarkManager = manager;
275 d->m_syncTimer.start();
276 d->downloadTimestamp();
279 QUrl BookmarkSyncManager::Private::endpointUrl(
const QString &endpoint )
const
281 return QUrl(
QString(
"%0/%1" ).arg( m_cloudSyncManager->apiUrl().toString() ).arg( endpoint ) );
284 void BookmarkSyncManager::Private::uploadBookmarks()
288 QString word =
"----MarbleCloudBoundary";
291 request.setHeader( QNetworkRequest::ContentTypeHeader,
QString(
"multipart/form-data; boundary=%0" ).arg( word ) );
294 data.
append(
"Content-Disposition: form-data; name=\"bookmarks\"; filename=\"bookmarks.kml\"" + lineBreak );
295 data.
append(
"Content-Type: application/vnd.google-earth.kml+xml" + lineBreak + lineBreak );
297 QFile bookmarksFile( m_localBookmarksPath );
298 if( !bookmarksFile.open( QFile::ReadOnly ) ) {
299 mDebug() <<
"Failed to open file" << bookmarksFile.fileName()
300 <<
". It is either missing or not readable.";
304 QByteArray kmlContent = bookmarksFile.readAll();
305 data.
append( kmlContent + lineBreak + lineBreak );
307 bookmarksFile.close();
309 m_uploadReply = m_network.post( request, data );
310 connect( m_uploadReply, SIGNAL(uploadProgress(qint64,qint64)),
311 m_q, SIGNAL(uploadProgress(qint64,qint64)) );
312 connect( m_uploadReply, SIGNAL(finished()),
313 m_q, SLOT(completeUpload()) );
316 void BookmarkSyncManager::Private::downloadBookmarks()
319 m_downloadReply = m_network.get( request );
320 connect( m_downloadReply, SIGNAL(finished()),
321 m_q, SLOT(completeSynchronization()) );
322 connect( m_downloadReply, SIGNAL(downloadProgress(qint64,qint64)),
323 m_q, SIGNAL(downloadProgress(qint64,qint64)) );
326 void BookmarkSyncManager::Private::downloadTimestamp()
328 mDebug() <<
"Determining remote bookmark state.";
329 m_timestampReply = m_network.get(
QNetworkRequest( endpointUrl( m_timestampEndpoint ) ) );
330 connect( m_timestampReply, SIGNAL(finished()),
331 m_q, SLOT(parseTimestamp()) );
334 bool BookmarkSyncManager::Private::cloudBookmarksModified(
const QString &cloudTimestamp )
const
340 QDir::NoFilter, QDir::Name );
343 lastSynced.
chop( 4 );
344 return cloudTimestamp != lastSynced;
350 void BookmarkSyncManager::Private::clearCache()
352 QDir cacheDir( m_cachePath );
353 QFileInfoList fileInfoList = cacheDir.entryInfoList(
355 QDir::NoFilter, QDir::Name );
356 if( !fileInfoList.isEmpty() ) {
357 foreach (
QFileInfo fileInfo, fileInfoList ) {
359 bool removed = file.
remove();
361 mDebug() <<
"Could not delete" << file.fileName() <<
362 "Make sure you have sufficient permissions.";
368 QString BookmarkSyncManager::Private::lastSyncedKmlPath()
const
370 QDir cacheDir( m_cachePath );
371 QFileInfoList fileInfoList = cacheDir.entryInfoList(
373 QDir::NoFilter, QDir::Name );
374 if( !fileInfoList.isEmpty() ) {
375 return fileInfoList.last().absoluteFilePath();
384 foreach ( GeoDataFolder *folder, document->folderList() ) {
386 diffItems.
append( getPlacemarks( folder, path, other, diffDirection ) );
395 foreach ( GeoDataFolder *folder, folder->folderList() ) {
397 diffItems.
append( getPlacemarks( folder, newPath, other, diffDirection ) );
400 foreach( GeoDataPlacemark *placemark, folder->placemarkList() ) {
402 diffItem.m_path = path;
403 diffItem.m_placemarkA = *placemark;
404 switch ( diffDirection ) {
405 case DiffItem::Source:
406 diffItem.m_origin = DiffItem::Destination;
408 case DiffItem::Destination:
409 diffItem.m_origin = DiffItem::Source;
415 determineDiffStatus( diffItem, other );
417 if( !( diffItem.m_action == DiffItem::NoAction && diffItem.m_origin == DiffItem::Destination )
418 && !( diffItem.m_action == DiffItem::Changed && diffItem.m_origin == DiffItem::Source ) ) {
419 diffItems.
append( diffItem );
426 const GeoDataPlacemark* BookmarkSyncManager::Private::findPlacemark( GeoDataContainer* container,
const GeoDataPlacemark &bookmark )
const
428 foreach( GeoDataPlacemark* placemark, container->placemarkList() ) {
434 foreach( GeoDataFolder* folder, container->folderList() ) {
435 const GeoDataPlacemark* placemark = findPlacemark( folder, bookmark );
444 void BookmarkSyncManager::Private::determineDiffStatus( DiffItem &item, GeoDataDocument *document )
const
446 const GeoDataPlacemark *match = findPlacemark( document, item.m_placemarkA );
449 item.m_placemarkB = *match;
450 bool nameChanged = item.m_placemarkA.name() != item.m_placemarkB.name();
451 bool descChanged = item.m_placemarkA.description() != item.m_placemarkB.description();
452 bool lookAtChanged = item.m_placemarkA.lookAt()->latitude() != item.m_placemarkB.lookAt()->latitude() ||
453 item.m_placemarkA.lookAt()->longitude() != item.m_placemarkB.lookAt()->longitude() ||
454 item.m_placemarkA.lookAt()->altitude() != item.m_placemarkB.lookAt()->altitude() ||
455 item.m_placemarkA.lookAt()->range() != item.m_placemarkB.lookAt()->range();
456 if( nameChanged || descChanged || lookAtChanged ) {
457 item.m_action = DiffItem::Changed;
459 item.m_action = DiffItem::NoAction;
462 switch( item.m_origin ) {
463 case DiffItem::Source:
464 item.m_action = DiffItem::Deleted;
465 item.m_placemarkB = item.m_placemarkA;
467 case DiffItem::Destination:
468 item.m_action = DiffItem::Created;
477 QFile fileB( destinationPath );
478 if( !fileB.open( QFile::ReadOnly ) ) {
479 mDebug() <<
"Could not open file " << fileB.fileName();
481 return diff( sourcePath, &fileB );
486 QFile fileA( sourcePath );
487 if( !fileA.open( QFile::ReadOnly ) ) {
488 mDebug() <<
"Could not open file " << fileA.fileName();
491 return diff( &fileA, fileB );
496 QFile fileB( destinationPath );
497 if( !fileB.
open( QFile::ReadOnly ) ) {
498 mDebug() <<
"Could not open file " << fileB.fileName();
501 return diff( source, &fileB );
507 parserA.read( fileA );
508 GeoDataDocument *documentA =
dynamic_cast<GeoDataDocument*
>( parserA.releaseDocument() );
511 parserB.read( fileB );
512 GeoDataDocument *documentB =
dynamic_cast<GeoDataDocument*
>( parserB.releaseDocument() );
514 QList<DiffItem> diffItems = getPlacemarks( documentA, documentB, DiffItem::Destination );
515 diffItems.
append( getPlacemarks( documentB, documentA, DiffItem::Source ) );
518 for(
int i = 0; i < diffItems.
count(); i++ ) {
519 for(
int p = i + 1; p < diffItems.
count(); p++ ) {
520 if( ( diffItems[i].m_origin == DiffItem::Source )
521 && ( diffItems[i].m_action == DiffItem::NoAction )
524 && ( diffItems[i].m_path != diffItems[p].m_path ) ) {
525 diffItems[p].m_action = DiffItem::Changed;
533 void BookmarkSyncManager::Private::merge()
535 foreach(
const DiffItem &itemA, m_diffA ) {
536 if( itemA.m_action == DiffItem::NoAction ) {
537 bool deleted =
false;
538 bool changed =
false;
541 foreach(
const DiffItem &itemB, m_diffB ) {
543 if( itemB.m_action == DiffItem::Deleted ) {
545 }
else if( itemB.m_action == DiffItem::Changed ) {
552 m_merged.append( other );
553 }
else if( !deleted ) {
554 m_merged.append( itemA );
556 }
else if( itemA.m_action == DiffItem::Created ) {
557 m_merged.append( itemA );
558 }
else if( itemA.m_action == DiffItem::Changed || itemA.m_action == DiffItem::Deleted ) {
559 bool conflict =
false;
562 foreach(
const DiffItem &itemB, m_diffB ) {
564 if( ( itemA.m_action == DiffItem::Changed && ( itemB.m_action == DiffItem::Changed || itemB.m_action == DiffItem::Deleted ) )
565 || ( itemA.m_action == DiffItem::Deleted && itemB.m_action == DiffItem::Changed ) ) {
572 if( !conflict && itemA.m_action == DiffItem::Changed ) {
573 m_merged.append( itemA );
574 }
else if ( conflict ) {
575 m_conflictItem = other;
576 MergeItem *mergeItem =
new MergeItem();
577 mergeItem->setPathA( itemA.m_path );
578 mergeItem->setPathB( other.m_path );
579 mergeItem->setPlacemarkA( itemA.m_placemarkA );
580 mergeItem->setPlacemarkB( other.m_placemarkA );
582 switch( itemA.m_action ) {
583 case DiffItem::Changed:
586 case DiffItem::Deleted:
593 switch( other.m_action ) {
594 case DiffItem::Changed:
597 case DiffItem::Deleted:
604 emit m_q->mergeConflict( mergeItem );
609 if( !m_diffA.isEmpty() ) {
610 m_diffA.removeFirst();
614 foreach(
const DiffItem &itemB, m_diffB ) {
615 if( itemB.m_action == DiffItem::Created ) {
616 m_merged.append( itemB );
623 GeoDataFolder* BookmarkSyncManager::Private::createFolders( GeoDataContainer *container,
QStringList &pathList )
625 GeoDataFolder *folder = 0;
626 if( pathList.
count() > 0 ) {
629 foreach( GeoDataFolder *otherFolder, container->folderList() ) {
630 if( otherFolder->name() == name ) {
631 folder = otherFolder;
636 folder =
new GeoDataFolder();
637 folder->setName( name );
638 container->append( folder );
641 if( pathList.
count() == 0 ) {
646 return createFolders( folder, pathList );
649 GeoDataDocument* BookmarkSyncManager::Private::constructDocument(
const QList<DiffItem> &mergedList )
651 GeoDataDocument *document =
new GeoDataDocument();
652 document->setName( tr(
"Bookmarks" ) );
654 foreach(
const DiffItem &item, mergedList ) {
655 GeoDataPlacemark *placemark =
new GeoDataPlacemark( item.m_placemarkA );
656 QStringList splitten = item.m_path.split(
'/', QString::SkipEmptyParts );
657 GeoDataFolder *folder = createFolders( document, splitten );
658 folder->append( placemark );
670 if( !d->m_diffA.isEmpty() ) {
671 diffItem = d->m_diffA.first();
675 diffItem = d->m_conflictItem;
681 if( diffItem.m_action != DiffItem::Deleted ) {
682 d->m_merged.append( diffItem );
685 if( !d->m_diffA.isEmpty() ) {
686 d->m_diffA.removeFirst();
692 void BookmarkSyncManager::Private::saveDownloadedToCache(
const QByteArray &kml )
694 QString localBookmarksDir = m_localBookmarksPath;
696 QFile bookmarksFile( m_localBookmarksPath );
697 if( !bookmarksFile.open( QFile::ReadWrite ) ) {
698 mDebug() <<
"Failed to open file" << bookmarksFile.fileName()
699 <<
". It is either missing or not readable.";
703 bookmarksFile.write( kml );
704 bookmarksFile.close();
708 void BookmarkSyncManager::Private::parseTimestamp()
710 QString response = m_timestampReply->readAll();
714 m_cloudTimestamp = timestamp;
715 mDebug() <<
"Remote bookmark timestamp is " << m_cloudTimestamp;
716 continueSynchronization();
718 void BookmarkSyncManager::Private::copyLocalToCache()
723 QFile bookmarksFile( m_localBookmarksPath );
724 bookmarksFile.copy(
QString(
"%0/%1.kml" ).arg( m_cachePath, m_cloudTimestamp ) );
726 disconnect( m_bookmarkManager, SIGNAL(bookmarksChanged()), m_q, SLOT(startBookmarkSync()) );
727 m_bookmarkManager->loadFile(
"bookmarks/bookmarks.kml" );
728 connect( m_bookmarkManager, SIGNAL(bookmarksChanged()), m_q, SLOT(startBookmarkSync()) );
732 void BookmarkSyncManager::Private::continueSynchronization()
734 bool cloudModified = cloudBookmarksModified( m_cloudTimestamp );
735 if( cloudModified ) {
738 QString lastSyncedPath = lastSyncedKmlPath();
739 if( lastSyncedPath.
isEmpty() ) {
740 mDebug() <<
"Never synced. Uploading bookmarks.";
743 QList<DiffItem> diffList = diff( lastSyncedPath, m_localBookmarksPath );
744 bool localModified =
false;
745 foreach(
const DiffItem &item, diffList ) {
746 if( item.m_action != DiffItem::NoAction ) {
747 localModified =
true;
751 if( localModified ) {
752 mDebug() <<
"Local modifications, uploading.";
759 void BookmarkSyncManager::Private::completeSynchronization()
761 mDebug() <<
"Merging remote and local bookmark file";
762 QString lastSyncedPath = lastSyncedKmlPath();
763 QFile localBookmarksFile( m_localBookmarksPath );
764 QByteArray result = m_downloadReply->readAll();
766 buffer.open( QIODevice::ReadOnly );
768 if( lastSyncedPath.
isEmpty() ) {
769 if( localBookmarksFile.exists() ) {
770 mDebug() <<
"Conflict between remote bookmarks and local ones";
771 m_diffA = diff( &buffer, m_localBookmarksPath );
772 m_diffB = diff( m_localBookmarksPath, &buffer );
774 saveDownloadedToCache( result );
780 m_diffA = diff( lastSyncedPath, m_localBookmarksPath );
781 m_diffB = diff( lastSyncedPath, &buffer );
788 void BookmarkSyncManager::Private::completeMerge()
790 QFile localBookmarksFile( m_localBookmarksPath );
791 GeoDataDocument *doc = constructDocument( m_merged );
793 localBookmarksFile.remove();
794 localBookmarksFile.open( QFile::ReadWrite );
795 writer.write( &localBookmarksFile, doc );
796 localBookmarksFile.close();
800 void BookmarkSyncManager::Private::completeUpload()
802 QString response = m_uploadReply->readAll();
806 m_cloudTimestamp = timestamp;
807 mDebug() <<
"Uploaded bookmarks to remote server. Timestamp is " << m_cloudTimestamp;
809 emit m_q->syncComplete();
814 #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.
QScriptValue evaluate(const QString &program, const QString &fileName, int lineNumber)
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.
QString & remove(int position, int n)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
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...
int count(const T &value) const
void append(const T &value)
QString absoluteFilePath() const
BookmarkSyncManager(CloudSyncManager *cloudSyncManager)
QScriptValue property(const QString &name, const ResolveFlags &mode) const
QByteArray & append(char ch)
bool mkdir(const QString &dirName) const
QStringList entryList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
QDateTime lastSync() const
Last time Marble synced everything up.
void bookmarkSyncEnabledChanged(bool enabled)
QDateTime created() const
void resolveConflict(MergeItem *item)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QDebug mDebug()
a function to replace qDebug() in Marble library code
bool mkpath(const QString &dirPath) const