Marble

BookmarkSyncManager.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2013 Utku Aydın <utkuaydin34@gmail.com>
4//
5
6#include "BookmarkSyncManager.h"
7
8#include "GeoWriter.h"
9#include "MarbleDirs.h"
10#include "MarbleDebug.h"
11#include "GeoDataParser.h"
12#include "GeoDataFolder.h"
13#include "GeoDataDocument.h"
14#include "GeoDataLookAt.h"
15#include "CloudSyncManager.h"
16#include "GeoDataCoordinates.h"
17#include "OwncloudSyncBackend.h"
18#include "MarbleModel.h"
19#include "BookmarkManager.h"
20
21#include <QFile>
22#include <QBuffer>
23#include <QJsonDocument>
24#include <QJsonObject>
25#include <QNetworkAccessManager>
26#include <QNetworkReply>
27#include <QTimer>
28
29namespace Marble {
30
31class DiffItem
32{
33public:
34 enum Action {
35 NoAction,
36 Created,
37 Changed,
38 Deleted
39 };
40
41 enum Status {
42 Source,
43 Destination
44 };
45
46 QString m_path;
47 Action m_action;
48 Status m_origin;
49 GeoDataPlacemark m_placemarkA;
50 GeoDataPlacemark m_placemarkB;
51};
52
53class Q_DECL_HIDDEN BookmarkSyncManager::Private
54{
55public:
56 Private( BookmarkSyncManager* parent, CloudSyncManager *cloudSyncManager );
57
58 BookmarkSyncManager* m_q;
59 CloudSyncManager *m_cloudSyncManager;
60
61 QNetworkAccessManager m_network;
62 QString m_uploadEndpoint;
63 QString m_downloadEndpoint;
64 QString m_timestampEndpoint;
65
66 QNetworkReply* m_uploadReply;
67 QNetworkReply* m_downloadReply;
68 QNetworkReply* m_timestampReply;
69
70 QString m_cloudTimestamp;
71
72 QString m_cachePath;
73 QString m_localBookmarksPath;
74 QString m_bookmarksTimestamp;
75
76 QList<DiffItem> m_diffA;
77 QList<DiffItem> m_diffB;
78 QList<DiffItem> m_merged;
79 DiffItem m_conflictItem;
80
81 BookmarkManager* m_bookmarkManager;
82 QTimer m_syncTimer;
83 bool m_bookmarkSyncEnabled;
84
85 /**
86 * Returns an API endpoint
87 * @param endpoint Endpoint itself without server info
88 * @return Complete API URL as QUrl
89 */
90 QUrl endpointUrl( const QString &endpoint ) const;
91
92 /**
93 * Uploads local bookmarks.kml to cloud.
94 */
95 void uploadBookmarks();
96
97 /**
98 * Downloads bookmarks.kml from cloud.
99 */
100 void downloadBookmarks();
101
102 /**
103 * Gets cloud bookmarks.kml's timestamp from cloud.
104 */
105 void downloadTimestamp();
106
107 /**
108 * Compares cloud bookmarks.kml's timestamp to last synced bookmarks.kml's timestamp.
109 * @return true if cloud one is different from last synced one.
110 */
111 bool cloudBookmarksModified( const QString &cloudTimestamp ) const;
112
113 /**
114 * Removes all KMLs in the cache except the
115 * one with youngest timestamp.
116 */
117 void clearCache();
118
119 /**
120 * Finds the last synced bookmarks.kml file and returns its path
121 * @return Path of last synced bookmarks.kml file.
122 */
123 QString lastSyncedKmlPath() const;
124
125 /**
126 * Gets all placemarks in a document as DiffItems, compares them to another document and puts the result in a list.
127 * @param document The document whose placemarks will be compared to another document's placemarks.
128 * @param other The document whose placemarks will be compared to the first document's placemarks.
129 * @param diffDirection Direction of comparison, e.g. must be DiffItem::Destination if direction is source to destination.
130 * @return A list of DiffItems
131 */
132 QList<DiffItem> getPlacemarks(GeoDataDocument *document, GeoDataDocument *other, DiffItem::Status diffDirection );
133
134 /**
135 * Gets all placemarks in a document as DiffItems, compares them to another document and puts the result in a list.
136 * @param folder The folder whose placemarks will be compared to another document's placemarks.
137 * @param path Path of the folder.
138 * @param other The document whose placemarks will be compared to the first document's placemarks.
139 * @param diffDirection Direction of comparison, e.g. must be DiffItem::Destination if direction is source to destination.
140 * @return A list of DiffItems
141 */
142 QList<DiffItem> getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other, DiffItem::Status diffDirection );
143
144 /**
145 * Finds the placemark which has the same coordinates with given bookmark
146 * @param container Container of placemarks which will be compared. Can be document or folder.
147 * @param bookmark The bookmark whose counterpart will be searched in the container.
148 * @return Counterpart of the given placemark.
149 */
150 const GeoDataPlacemark* findPlacemark( GeoDataContainer* container, const GeoDataPlacemark &bookmark ) const;
151
152 /**
153 * Determines the status (created, deleted, changed or unchanged) of given DiffItem
154 * by comparing the item's placemark with placemarks of given GeoDataDocument.
155 * @param item The item whose status will be determined.
156 * @param document The document whose placemarks will be used to determine DiffItem's status.
157 */
158 void determineDiffStatus( DiffItem &item, GeoDataDocument* document ) const;
159
160 /**
161 * Finds differences between two bookmark files.
162 * @param sourcePath Source bookmark
163 * @param destinationPath Destination bookmark
164 * @return A list of differences
165 */
166 QList<DiffItem> diff( QString &sourcePath, QString &destinationPath );
167 QList<DiffItem> diff( QString &sourcePath, QIODevice* destination );
168 QList<DiffItem> diff( QIODevice* source, QString &destinationPath );
169 QList<DiffItem> diff( QIODevice *source, QIODevice* destination );
170
171 /**
172 * Merges two diff lists.
173 * @param diffListA First diff list.
174 * @param diffListB Second diff list.
175 * @return Merged DiffItems.
176 */
177 void merge();
178
179 /**
180 * Creates GeoDataFolders using strings in path list.
181 * @param container Container which created GeoDataFolder will be attached to.
182 * @param pathList Names of folders. Note that each item will be the child of the previous one.
183 * @return A pointer to created folder.
184 */
185 GeoDataFolder* createFolders( GeoDataContainer *container, QStringList &pathList );
186
187 /**
188 * Creates a GeoDataDocument using a list of DiffItems.
189 * @param mergedList DiffItems which will be used as placemarks.
190 * @return A pointer to created document.
191 */
192 GeoDataDocument* constructDocument( const QList<DiffItem> &mergedList );
193
194 void saveDownloadedToCache( const QByteArray &kml );
195
196 void parseTimestamp();
197 void copyLocalToCache();
198
199 void continueSynchronization();
200 void completeSynchronization();
201 void completeMerge();
202 void completeUpload();
203};
204
205BookmarkSyncManager::Private::Private(BookmarkSyncManager *parent, CloudSyncManager *cloudSyncManager ) :
206 m_q( parent ),
207 m_cloudSyncManager( cloudSyncManager ),
208 m_bookmarkManager( nullptr ),
209 m_bookmarkSyncEnabled( false )
210{
211 m_cachePath = MarbleDirs::localPath() + QLatin1String("/cloudsync/cache/bookmarks");
212 m_localBookmarksPath = MarbleDirs::localPath() + QLatin1String("/bookmarks/bookmarks.kml");
213 m_downloadEndpoint = "bookmarks/kml";
214 m_uploadEndpoint = "bookmarks/update";
215 m_timestampEndpoint = "bookmarks/timestamp";
216}
217
218BookmarkSyncManager::BookmarkSyncManager( CloudSyncManager *cloudSyncManager ) :
219 QObject(),
220 d( new Private( this, cloudSyncManager ) )
221{
222 d->m_syncTimer.setInterval( 60 * 60 * 1000 ); // 1 hour. TODO: Make this configurable.
223 connect( &d->m_syncTimer, SIGNAL(timeout()), this, SLOT(startBookmarkSync()) );
224}
225
226BookmarkSyncManager::~BookmarkSyncManager()
227{
228 delete d;
229}
230
231QDateTime BookmarkSyncManager::lastSync() const
232{
233 const QString last = d->lastSyncedKmlPath();
234 if (last.isEmpty())
235 return QDateTime();
236 return QFileInfo(last).metadataChangeTime();
237}
238
239bool BookmarkSyncManager::isBookmarkSyncEnabled() const
240{
241 return d->m_bookmarkSyncEnabled;
242}
243
244void BookmarkSyncManager::setBookmarkSyncEnabled( bool enabled )
245{
246 bool const old_state = isBookmarkSyncEnabled();
247 d->m_bookmarkSyncEnabled = enabled;
248 if ( old_state != isBookmarkSyncEnabled() ) {
249 emit bookmarkSyncEnabledChanged( d->m_bookmarkSyncEnabled );
250 if ( isBookmarkSyncEnabled() ) {
251 startBookmarkSync();
252 }
253 }
254}
255
256void BookmarkSyncManager::setBookmarkManager(BookmarkManager *manager)
257{
258 d->m_bookmarkManager = manager;
259 connect( manager, SIGNAL(bookmarksChanged()), this, SLOT(startBookmarkSync()) );
260 startBookmarkSync();
261}
262
263void BookmarkSyncManager::startBookmarkSync()
264{
265 if ( !d->m_cloudSyncManager->isSyncEnabled() || !isBookmarkSyncEnabled() )
266 {
267 return;
268 }
269
270 d->m_syncTimer.start();
271 d->downloadTimestamp();
272}
273
274QUrl BookmarkSyncManager::Private::endpointUrl( const QString &endpoint ) const
275{
276 return QUrl(m_cloudSyncManager->apiUrl().toString() + QLatin1Char('/') + endpoint);
277}
278
279void BookmarkSyncManager::Private::uploadBookmarks()
280{
281 QByteArray data;
282 QByteArray lineBreak = "\r\n";
283 QString word = "----MarbleCloudBoundary";
284 QString boundary = QString( "--%0" ).arg( word );
285 QNetworkRequest request( endpointUrl( m_uploadEndpoint ) );
286 request.setHeader( QNetworkRequest::ContentTypeHeader, QString( "multipart/form-data; boundary=%0" ).arg( word ) );
287
288 data.append( QString( boundary + lineBreak ).toUtf8() );
289 data.append( "Content-Disposition: form-data; name=\"bookmarks\"; filename=\"bookmarks.kml\"" + lineBreak );
290 data.append( "Content-Type: application/vnd.google-earth.kml+xml" + lineBreak + lineBreak );
291
292 QFile bookmarksFile( m_localBookmarksPath );
293 if( !bookmarksFile.open( QFile::ReadOnly ) ) {
294 mDebug() << "Failed to open file" << bookmarksFile.fileName()
295 << ". It is either missing or not readable.";
296 return;
297 }
298
299 QByteArray kmlContent = bookmarksFile.readAll();
300 data.append( kmlContent + lineBreak + lineBreak );
301 data.append( QString( boundary ).toUtf8() );
302 bookmarksFile.close();
303
304 m_uploadReply = m_network.post( request, data );
305 connect( m_uploadReply, SIGNAL(uploadProgress(qint64,qint64)),
306 m_q, SIGNAL(uploadProgress(qint64,qint64)) );
307 connect( m_uploadReply, SIGNAL(finished()),
308 m_q, SLOT(completeUpload()) );
309}
310
311void BookmarkSyncManager::Private::downloadBookmarks()
312{
313 QNetworkRequest request( endpointUrl( m_downloadEndpoint ) );
314 m_downloadReply = m_network.get( request );
315 connect( m_downloadReply, SIGNAL(finished()),
316 m_q, SLOT(completeSynchronization()) );
317 connect( m_downloadReply, SIGNAL(downloadProgress(qint64,qint64)),
318 m_q, SIGNAL(downloadProgress(qint64,qint64)) );
319}
320
321void BookmarkSyncManager::Private::downloadTimestamp()
322{
323 mDebug() << "Determining remote bookmark state.";
324 m_timestampReply = m_network.get( QNetworkRequest( endpointUrl( m_timestampEndpoint ) ) );
325 connect( m_timestampReply, SIGNAL(finished()),
326 m_q, SLOT(parseTimestamp()) );
327}
328
329bool BookmarkSyncManager::Private::cloudBookmarksModified( const QString &cloudTimestamp ) const
330{
331 QStringList entryList = QDir( m_cachePath ).entryList(
332 // TODO: replace with regex filter that only
333 // allows timestamp filenames
334 QStringList() << "*.kml",
336 if( !entryList.isEmpty() ) {
337 QString lastSynced = entryList.last();
338 lastSynced.chop( 4 );
339 return cloudTimestamp != lastSynced;
340 } else {
341 return true; // That will let cloud one get downloaded.
342 }
343}
344
345void BookmarkSyncManager::Private::clearCache()
346{
347 QDir cacheDir( m_cachePath );
348 QFileInfoList fileInfoList = cacheDir.entryInfoList(
349 QStringList() << "*.kml",
351 if( !fileInfoList.isEmpty() ) {
352 for ( const QFileInfo& fileInfo: fileInfoList ) {
353 QFile file( fileInfo.absoluteFilePath() );
354 bool removed = file.remove();
355 if( !removed ) {
356 mDebug() << "Could not delete" << file.fileName() <<
357 "Make sure you have sufficient permissions.";
358 }
359 }
360 }
361}
362
363QString BookmarkSyncManager::Private::lastSyncedKmlPath() const
364{
365 QDir cacheDir( m_cachePath );
366 QFileInfoList fileInfoList = cacheDir.entryInfoList(
367 QStringList() << "*.kml",
369 if( !fileInfoList.isEmpty() ) {
370 return fileInfoList.last().absoluteFilePath();
371 } else {
372 return QString();
373 }
374}
375
376QList<DiffItem> BookmarkSyncManager::Private::getPlacemarks( GeoDataDocument *document, GeoDataDocument *other, DiffItem::Status diffDirection )
377{
378 QList<DiffItem> diffItems;
379 for ( GeoDataFolder *folder: document->folderList() ) {
380 QString path = QString( "/%0" ).arg( folder->name() );
381 diffItems.append( getPlacemarks( folder, path, other, diffDirection ) );
382 }
383
384 return diffItems;
385}
386
387QList<DiffItem> BookmarkSyncManager::Private::getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other, DiffItem::Status diffDirection )
388{
389 QList<DiffItem> diffItems;
390 for ( GeoDataFolder *subFolder: folder->folderList() ) {
391 QString newPath = QString( "%0/%1" ).arg( path, subFolder->name() );
392 diffItems.append( getPlacemarks( subFolder, newPath, other, diffDirection ) );
393 }
394
395 for( GeoDataPlacemark *placemark: folder->placemarkList() ) {
396 DiffItem diffItem;
397 diffItem.m_path = path;
398 diffItem.m_placemarkA = *placemark;
399 switch ( diffDirection ) {
400 case DiffItem::Source:
401 diffItem.m_origin = DiffItem::Destination;
402 break;
403 case DiffItem::Destination:
404 diffItem.m_origin = DiffItem::Source;
405 break;
406 default:
407 break;
408 }
409
410 determineDiffStatus( diffItem, other );
411
412 if( !( diffItem.m_action == DiffItem::NoAction && diffItem.m_origin == DiffItem::Destination )
413 && !( diffItem.m_action == DiffItem::Changed && diffItem.m_origin == DiffItem::Source ) ) {
414 diffItems.append( diffItem );
415 }
416 }
417
418 return diffItems;
419}
420
421const GeoDataPlacemark* BookmarkSyncManager::Private::findPlacemark( GeoDataContainer* container, const GeoDataPlacemark &bookmark ) const
422{
423 for( GeoDataPlacemark* placemark: container->placemarkList() ) {
424 if (EARTH_RADIUS * placemark->coordinate().sphericalDistanceTo(bookmark.coordinate()) <= 1) {
425 return placemark;
426 }
427 }
428
429 for( GeoDataFolder* folder: container->folderList() ) {
430 const GeoDataPlacemark* placemark = findPlacemark( folder, bookmark );
431 if ( placemark ) {
432 return placemark;
433 }
434 }
435
436 return nullptr;
437}
438
439void BookmarkSyncManager::Private::determineDiffStatus( DiffItem &item, GeoDataDocument *document ) const
440{
441 const GeoDataPlacemark *match = findPlacemark( document, item.m_placemarkA );
442
443 if( match != nullptr ) {
444 item.m_placemarkB = *match;
445 bool nameChanged = item.m_placemarkA.name() != item.m_placemarkB.name();
446 bool descChanged = item.m_placemarkA.description() != item.m_placemarkB.description();
447 bool lookAtChanged = item.m_placemarkA.lookAt()->latitude() != item.m_placemarkB.lookAt()->latitude() ||
448 item.m_placemarkA.lookAt()->longitude() != item.m_placemarkB.lookAt()->longitude() ||
449 item.m_placemarkA.lookAt()->altitude() != item.m_placemarkB.lookAt()->altitude() ||
450 item.m_placemarkA.lookAt()->range() != item.m_placemarkB.lookAt()->range();
451 if( nameChanged || descChanged || lookAtChanged ) {
452 item.m_action = DiffItem::Changed;
453 } else {
454 item.m_action = DiffItem::NoAction;
455 }
456 } else {
457 switch( item.m_origin ) {
458 case DiffItem::Source:
459 item.m_action = DiffItem::Deleted;
460 item.m_placemarkB = item.m_placemarkA; // for conflict purposes
461 break;
462 case DiffItem::Destination:
463 item.m_action = DiffItem::Created;
464 break;
465 }
466
467 }
468}
469
470QList<DiffItem> BookmarkSyncManager::Private::diff( QString &sourcePath, QString &destinationPath )
471{
472 QFile fileB( destinationPath );
473 if( !fileB.open( QFile::ReadOnly ) ) {
474 mDebug() << "Could not open file " << fileB.fileName();
475 }
476 return diff( sourcePath, &fileB );
477}
478
479QList<DiffItem> BookmarkSyncManager::Private::diff( QString &sourcePath, QIODevice *fileB )
480{
481 QFile fileA( sourcePath );
482 if( !fileA.open( QFile::ReadOnly ) ) {
483 mDebug() << "Could not open file " << fileA.fileName();
484 }
485
486 return diff( &fileA, fileB );
487}
488
489QList<DiffItem> BookmarkSyncManager::Private::diff( QIODevice *source, QString &destinationPath )
490{
491 QFile fileB( destinationPath );
492 if( !fileB.open( QFile::ReadOnly ) ) {
493 mDebug() << "Could not open file " << fileB.fileName();
494 }
495
496 return diff( source, &fileB );
497}
498
499QList<DiffItem> BookmarkSyncManager::Private::diff( QIODevice *fileA, QIODevice *fileB )
500{
501 GeoDataParser parserA( GeoData_KML );
502 parserA.read( fileA );
503 GeoDataDocument *documentA = dynamic_cast<GeoDataDocument*>( parserA.releaseDocument() );
504
505 GeoDataParser parserB( GeoData_KML );
506 parserB.read( fileB );
507 GeoDataDocument *documentB = dynamic_cast<GeoDataDocument*>( parserB.releaseDocument() );
508
509 QList<DiffItem> diffItems = getPlacemarks( documentA, documentB, DiffItem::Destination ); // Compare old to new
510 diffItems.append( getPlacemarks( documentB, documentA, DiffItem::Source ) ); // Compare new to old
511
512 // Compare paths
513 for( int i = 0; i < diffItems.count(); i++ ) {
514 for( int p = i + 1; p < diffItems.count(); p++ ) {
515 if( ( diffItems[i].m_origin == DiffItem::Source )
516 && ( diffItems[i].m_action == DiffItem::NoAction )
517 && ( EARTH_RADIUS * diffItems[i].m_placemarkA.coordinate().sphericalDistanceTo(diffItems[p].m_placemarkB.coordinate()) <= 1 )
518 && ( EARTH_RADIUS * diffItems[i].m_placemarkB.coordinate().sphericalDistanceTo(diffItems[p].m_placemarkA.coordinate()) <= 1 )
519 && ( diffItems[i].m_path != diffItems[p].m_path ) ) {
520 diffItems[p].m_action = DiffItem::Changed;
521 }
522 }
523 }
524
525 return diffItems;
526}
527
528void BookmarkSyncManager::Private::merge()
529{
530 for( const DiffItem &itemA: m_diffA ) {
531 if( itemA.m_action == DiffItem::NoAction ) {
532 bool deleted = false;
533 bool changed = false;
534 DiffItem other;
535
536 for( const DiffItem &itemB: m_diffB ) {
537 if( EARTH_RADIUS * itemA.m_placemarkA.coordinate().sphericalDistanceTo(itemB.m_placemarkA.coordinate()) <= 1 ) {
538 if( itemB.m_action == DiffItem::Deleted ) {
539 deleted = true;
540 } else if( itemB.m_action == DiffItem::Changed ) {
541 changed = true;
542 other = itemB;
543 }
544 }
545 }
546 if( changed ) {
547 m_merged.append( other );
548 } else if( !deleted ) {
549 m_merged.append( itemA );
550 }
551 } else if( itemA.m_action == DiffItem::Created ) {
552 m_merged.append( itemA );
553 } else if( itemA.m_action == DiffItem::Changed || itemA.m_action == DiffItem::Deleted ) {
554 bool conflict = false;
555 DiffItem other;
556
557 for( const DiffItem &itemB: m_diffB ) {
558 if (EARTH_RADIUS * itemA.m_placemarkB.coordinate().sphericalDistanceTo(itemB.m_placemarkB.coordinate()) <= 1) {
559 if( ( itemA.m_action == DiffItem::Changed && ( itemB.m_action == DiffItem::Changed || itemB.m_action == DiffItem::Deleted ) )
560 || ( itemA.m_action == DiffItem::Deleted && itemB.m_action == DiffItem::Changed ) ) {
561 conflict = true;
562 other = itemB;
563 }
564 }
565 }
566
567 if( !conflict && itemA.m_action == DiffItem::Changed ) {
568 m_merged.append( itemA );
569 } else if ( conflict ) {
570 m_conflictItem = other;
571 MergeItem *mergeItem = new MergeItem();
572 mergeItem->setPathA( itemA.m_path );
573 mergeItem->setPathB( other.m_path );
574 mergeItem->setPlacemarkA( itemA.m_placemarkA );
575 mergeItem->setPlacemarkB( other.m_placemarkA );
576
577 switch( itemA.m_action ) {
578 case DiffItem::Changed:
579 mergeItem->setActionA( MergeItem::Changed );
580 break;
581 case DiffItem::Deleted:
582 mergeItem->setActionA( MergeItem::Deleted );
583 break;
584 default:
585 break;
586 }
587
588 switch( other.m_action ) {
589 case DiffItem::Changed:
590 mergeItem->setActionB( MergeItem::Changed );
591 break;
592 case DiffItem::Deleted:
593 mergeItem->setActionB( MergeItem::Deleted );
594 break;
595 default:
596 break;
597 }
598
599 emit m_q->mergeConflict( mergeItem );
600 return;
601 }
602 }
603
604 if( !m_diffA.isEmpty() ) {
605 m_diffA.removeFirst();
606 }
607 }
608
609 for( const DiffItem &itemB: m_diffB ) {
610 if( itemB.m_action == DiffItem::Created ) {
611 m_merged.append( itemB );
612 }
613 }
614
615 completeMerge();
616}
617
618GeoDataFolder* BookmarkSyncManager::Private::createFolders( GeoDataContainer *container, QStringList &pathList )
619{
620 GeoDataFolder *folder = nullptr;
621 if( pathList.count() > 0 ) {
622 QString name = pathList.takeFirst();
623
624 for( GeoDataFolder *otherFolder: container->folderList() ) {
625 if( otherFolder->name() == name ) {
626 folder = otherFolder;
627 }
628 }
629
630 if( folder == nullptr ) {
631 folder = new GeoDataFolder();
632 folder->setName( name );
633 container->append( folder );
634 }
635
636 if( pathList.count() == 0 ) {
637 return folder;
638 }
639 }
640
641 return createFolders( folder, pathList );
642}
643
644GeoDataDocument* BookmarkSyncManager::Private::constructDocument( const QList<DiffItem> &mergedList )
645{
646 GeoDataDocument *document = new GeoDataDocument();
647 document->setName( tr( "Bookmarks" ) );
648
649 for( const DiffItem &item: mergedList ) {
650 GeoDataPlacemark *placemark = new GeoDataPlacemark( item.m_placemarkA );
651 QStringList splitten = item.m_path.split(QLatin1Char('/'), QString::SkipEmptyParts);
652 GeoDataFolder *folder = createFolders( document, splitten );
653 folder->append( placemark );
654 }
655
656 return document;
657}
658
659void BookmarkSyncManager::resolveConflict( MergeItem *item )
660{
661 DiffItem diffItem;
662
663 switch( item->resolution() ) {
664 case MergeItem::A:
665 if( !d->m_diffA.isEmpty() ) {
666 diffItem = d->m_diffA.first();
667 break;
668 }
669 Q_FALLTHROUGH();
670 case MergeItem::B:
671 diffItem = d->m_conflictItem;
672 break;
673 default:
674 return; // It shouldn't happen.
675 }
676
677 if( diffItem.m_action != DiffItem::Deleted ) {
678 d->m_merged.append( diffItem );
679 }
680
681 if( !d->m_diffA.isEmpty() ) {
682 d->m_diffA.removeFirst();
683 }
684
685 d->merge();
686}
687
688void BookmarkSyncManager::Private::saveDownloadedToCache( const QByteArray &kml )
689{
690 QString localBookmarksDir = m_localBookmarksPath;
691 QDir().mkdir( localBookmarksDir.remove( "bookmarks.kml" ) );
692 QFile bookmarksFile( m_localBookmarksPath );
693 if( !bookmarksFile.open( QFile::ReadWrite ) ) {
694 mDebug() << "Failed to open file" << bookmarksFile.fileName()
695 << ". It is either missing or not readable.";
696 return;
697 }
698
699 bookmarksFile.write( kml );
700 bookmarksFile.close();
701 copyLocalToCache();
702}
703
704void BookmarkSyncManager::Private::parseTimestamp()
705{
706 QJsonDocument jsonDoc = QJsonDocument::fromJson(m_timestampReply->readAll());
707 QJsonValue dataValue = jsonDoc.object().value(QStringLiteral("data"));
708
709 m_cloudTimestamp = dataValue.toString();
710 mDebug() << "Remote bookmark timestamp is " << m_cloudTimestamp;
711 continueSynchronization();
712}
713void BookmarkSyncManager::Private::copyLocalToCache()
714{
715 QDir().mkpath( m_cachePath );
716 clearCache();
717
718 QFile bookmarksFile( m_localBookmarksPath );
719 bookmarksFile.copy( QString( "%0/%1.kml" ).arg( m_cachePath, m_cloudTimestamp ) );
720}
721
722// Bookmark synchronization steps
723void BookmarkSyncManager::Private::continueSynchronization()
724{
725 bool cloudModified = cloudBookmarksModified( m_cloudTimestamp );
726 if( cloudModified ) {
727 downloadBookmarks();
728 } else {
729 QString lastSyncedPath = lastSyncedKmlPath();
730 if( lastSyncedPath.isEmpty() ) {
731 mDebug() << "Never synced. Uploading bookmarks.";
732 uploadBookmarks();
733 } else {
734 QList<DiffItem> diffList = diff( lastSyncedPath, m_localBookmarksPath );
735 bool localModified = false;
736 for( const DiffItem &item: diffList ) {
737 if( item.m_action != DiffItem::NoAction ) {
738 localModified = true;
739 }
740 }
741
742 if( localModified ) {
743 mDebug() << "Local modifications, uploading.";
744 uploadBookmarks();
745 }
746 }
747 }
748}
749
750void BookmarkSyncManager::Private::completeSynchronization()
751{
752 mDebug() << "Merging remote and local bookmark file";
753 QString lastSyncedPath = lastSyncedKmlPath();
754 QFile localBookmarksFile( m_localBookmarksPath );
755 QByteArray result = m_downloadReply->readAll();
756 QBuffer buffer( &result );
757 buffer.open( QIODevice::ReadOnly );
758
759 if( lastSyncedPath.isEmpty() ) {
760 if( localBookmarksFile.exists() ) {
761 mDebug() << "Conflict between remote bookmarks and local ones";
762 m_diffA = diff( &buffer, m_localBookmarksPath );
763 m_diffB = diff( m_localBookmarksPath, &buffer );
764 } else {
765 saveDownloadedToCache( result );
766 return;
767 }
768 }
769 else
770 {
771 m_diffA = diff( lastSyncedPath, m_localBookmarksPath );
772 m_diffB = diff( lastSyncedPath, &buffer );
773 }
774
775 m_merged.clear();
776 merge();
777}
778
779void BookmarkSyncManager::Private::completeMerge()
780{
781 QFile localBookmarksFile( m_localBookmarksPath );
782 GeoDataDocument *doc = constructDocument( m_merged );
783 GeoWriter writer;
784 localBookmarksFile.remove();
785 localBookmarksFile.open( QFile::ReadWrite );
786 writer.write( &localBookmarksFile, doc );
787 localBookmarksFile.close();
788 uploadBookmarks();
789}
790
791void BookmarkSyncManager::Private::completeUpload()
792{
793 QJsonDocument jsonDoc = QJsonDocument::fromJson(m_uploadReply->readAll());
794 QJsonValue dataValue = jsonDoc.object().value(QStringLiteral("data"));
795
796 m_cloudTimestamp = dataValue.toString();
797 mDebug() << "Uploaded bookmarks to remote server. Timestamp is " << m_cloudTimestamp;
798 copyLocalToCache();
799 emit m_q->syncComplete();
800}
801
802}
803
804#include "moc_BookmarkSyncManager.cpp"
This file contains the headers for MarbleModel.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString path(const QString &relativePath)
QStringView merge(QStringView lhs, QStringView rhs)
QString name(StandardAction id)
Binds a QML item to a specific geodetic location in screen coordinates.
QByteArray & append(QByteArrayView data)
QStringList entryList(Filters filters, SortFlags sort) const const
bool mkdir(const QString &dirName) const const
bool mkpath(const QString &dirPath) const const
QDateTime metadataChangeTime() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
T & last()
value_type takeFirst()
QString arg(Args &&... args) const const
void chop(qsizetype n)
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 12:00:06 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.