6#include "OwncloudSyncBackend.h"
8#include "CloudRouteModel.h"
9#include "CloudSyncManager.h"
10#include "GeoDataData.h"
11#include "GeoDataDocument.h"
12#include "GeoDataExtendedData.h"
13#include "GeoDataFolder.h"
14#include "GeoDataParser.h"
15#include "GeoDataPlacemark.h"
16#include "GeoDocument.h"
17#include "MarbleDebug.h"
18#include "MarbleDirs.h"
21#include "RenderPlugin.h"
24#include "RoutingManager.h"
25#include "RoutingModel.h"
31#include <QJsonDocument>
33#include <QNetworkAccessManager>
34#include <QNetworkRequest>
39class Q_DECL_HIDDEN OwncloudSyncBackend::Private
42 Private(CloudSyncManager *cloudSyncManager);
56 QString m_routeDownloadEndpoint;
60 CloudSyncManager *m_cloudSyncManager =
nullptr;
64OwncloudSyncBackend::Private::Private(CloudSyncManager *cloudSyncManager)
65 : m_cacheDir(MarbleDirs::localPath() +
QLatin1StringView(
"/cloudsync/cache/routes/"))
67 , m_routeUploadEndpoint(QStringLiteral(
"routes/create"))
68 , m_routeListEndpoint(QStringLiteral(
"routes"))
69 , m_routeDownloadEndpoint(QStringLiteral(
"routes"))
70 , m_routeDeleteEndpoint(QStringLiteral(
"routes/delete"))
71 , m_routePreviewEndpoint(QStringLiteral(
"routes/preview"))
72 , m_cloudSyncManager(cloudSyncManager)
76OwncloudSyncBackend::OwncloudSyncBackend(CloudSyncManager *cloudSyncManager)
77 : d(new Private(cloudSyncManager))
79 connect(d->m_cloudSyncManager, &CloudSyncManager::apiUrlChanged,
this, &OwncloudSyncBackend::validateSettings);
82OwncloudSyncBackend::~OwncloudSyncBackend()
87void OwncloudSyncBackend::uploadRoute(
const QString ×tamp)
89 QString word = QStringLiteral(
"----MarbleCloudBoundary");
90 QString boundary = QStringLiteral(
"--%0").
arg(word);
98 data.
append(
"Content-Disposition: form-data; name=\"timestamp\"");
100 data.
append(
QString(timestamp + QStringLiteral(
"\r\n")).toUtf8());
101 data.
append(
QString(boundary + QStringLiteral(
"\r\n")).toUtf8());
104 data.
append(
"Content-Disposition: form-data; name=\"name\"");
106 data.
append(routeName(timestamp).toUtf8());
110 QFile kmlFile(d->m_cacheDir.absolutePath() + QStringLiteral(
"/%0.kml").arg(timestamp));
113 mDebug() <<
"Could not open " << timestamp <<
".kml. Either it has not been saved"
114 <<
" to cache for upload or another application removed it from there.";
118 GeoDataParser parser(GeoData_KML);
119 if (!parser.read(&kmlFile)) {
120 mDebug() <<
"[OwncloudSyncBackend] KML file" << kmlFile.fileName() <<
"is broken so I can't fill required properties";
124 auto root =
dynamic_cast<GeoDataDocument *
>(parser.releaseDocument());
125 if (!root || root->size() < 2) {
126 mDebug() <<
"[OwncloudSyncBackend] Root document is broken";
130 auto doc = geodata_cast<GeoDataDocument>(root->child(1));
131 if (!doc || doc->size() < 1) {
132 mDebug() <<
"[OwncloudSyncBackend] Tracking document is broken";
136 auto placemark = geodata_cast<GeoDataPlacemark>(doc->child(0));
138 mDebug() <<
"[OwncloudSyncBackend] Placemark is broken";
144 mDebug() <<
"[Owncloud] Duration on write is" << duration;
145 data.
append(
"Content-Disposition: form-data; name=\"duration\"");
152 double distance = placemark->extendedData().value(QStringLiteral(
"length")).value().toDouble();
153 mDebug() <<
"[Owncloud] Distance on write is" <<
distance;
154 data.
append(
"Content-Disposition: form-data; name=\"distance\"");
161 data.
append(QStringLiteral(
"Content-Disposition: form-data; name=\"kml\"; filename=\"%0.kml\"").arg(timestamp).toUtf8());
163 data.
append(
"Content-Type: application/vnd.google-earth.kml+xml");
167 data.
append(kmlFile.readAll());
174 data.
append(QStringLiteral(
"Content-Disposition: form-data; name=\"preview\"; filename=\"%0.jpg\"").arg(timestamp).toUtf8());
176 data.
append(
"Content-Type: image/jpg");
180 QBuffer previewBuffer(&previewBytes);
181 QPixmap preview = createPreview(timestamp);
182 preview.
save(&previewBuffer,
"JPG");
184 data.
append(previewBytes);
188 d->m_routeUploadReply = d->m_network.post(request, data);
189 connect(d->m_routeUploadReply, SIGNAL(uploadProgress(qint64, qint64)),
this, SIGNAL(routeUploadProgress(qint64, qint64)));
192void OwncloudSyncBackend::downloadRouteList()
195 d->m_routeListReply = d->m_network.get(request);
196 connect(d->m_routeListReply, SIGNAL(downloadProgress(qint64, qint64)),
this, SIGNAL(routeListDownloadProgress(qint64, qint64)));
197 connect(d->m_routeListReply, SIGNAL(finished()),
this, SLOT(prepareRouteList()));
200void OwncloudSyncBackend::downloadRoute(
const QString ×tamp)
202 QNetworkRequest routeRequest(endpointUrl(d->m_routeDownloadEndpoint, timestamp));
203 d->m_routeDownloadReply = d->m_network.get(routeRequest);
204 connect(d->m_routeDownloadReply, SIGNAL(finished()),
this, SLOT(saveDownloadedRoute()));
205 connect(d->m_routeDownloadReply, SIGNAL(downloadProgress(qint64, qint64)),
this, SIGNAL(routeDownloadProgress(qint64, qint64)));
208void OwncloudSyncBackend::deleteRoute(
const QString ×tamp)
210 QUrl url(endpointUrl(d->m_routeDeleteEndpoint, timestamp));
212 d->m_routeDeleteReply = d->m_network.deleteResource(request);
213 connect(d->m_routeDeleteReply, SIGNAL(finished()),
this, SIGNAL(routeDeleted()));
216QPixmap OwncloudSyncBackend::createPreview(
const QString ×tamp)
const
218 MarbleWidget mapWidget;
219 for (RenderPlugin *plugin : mapWidget.renderPlugins()) {
220 plugin->setEnabled(
false);
223 mapWidget.setProjection(Mercator);
224 mapWidget.setMapThemeId(QStringLiteral(
"earth/openstreetmap/openstreetmap.dgml"));
225 mapWidget.resize(512, 512);
227 RoutingManager *manager = mapWidget.model()->routingManager();
228 manager->loadRoute(d->m_cacheDir.absolutePath() + QStringLiteral(
"/%0.kml").arg(timestamp));
229 GeoDataLatLonBox
const bbox = manager->routingModel()->route().bounds();
231 if (!bbox.isEmpty()) {
232 mapWidget.centerOn(bbox);
235 QPixmap pixmap = mapWidget.grab();
236 QDir(d->m_cacheDir.absolutePath()).
mkpath(QStringLiteral(
"preview"));
242QString OwncloudSyncBackend::routeName(
const QString ×tamp)
const
244 QFile file(d->m_cacheDir.absolutePath() + QStringLiteral(
"/%0.kml").arg(timestamp));
247 GeoDataParser parser(GeoData_KML);
248 if (!parser.read(&file)) {
249 mDebug() <<
"Could not read " << timestamp <<
".kml. Timestamp will be used as "
250 <<
"route name because of the problem";
256 GeoDocument *geoDoc = parser.releaseDocument();
257 auto container =
dynamic_cast<GeoDataDocument *
>(geoDoc);
258 if (container && !container->isEmpty()) {
259 GeoDataFolder *folder = container->folderList().at(0);
260 for (GeoDataPlacemark *placemark : folder->placemarkList()) {
261 routeName.
append(placemark->name());
262 routeName.
append(QStringLiteral(
" - "));
266 return routeName.
left(routeName.
length() - 3);
269void OwncloudSyncBackend::validateSettings()
271 if (!d->m_cloudSyncManager->owncloudServer().isEmpty() && !d->m_cloudSyncManager->owncloudUsername().isEmpty()
272 && !d->m_cloudSyncManager->owncloudPassword().isEmpty()) {
274 d->m_authReply = d->m_network.get(request);
279 d->m_cloudSyncManager->setStatus({}, CloudSyncManager::Success);
286 QString const status = tr(
"Server '%1' could not be reached").
arg(d->m_cloudSyncManager->owncloudServer());
287 d->m_cloudSyncManager->setStatus(
status, CloudSyncManager::Error);
291void OwncloudSyncBackend::checkAuthReply()
304 d->m_cloudSyncManager->setStatus(tr(
"The Marble app is not installed on the ownCloud server"), CloudSyncManager::Error);
306 d->m_cloudSyncManager->setStatus(tr(
"The server is not an ownCloud server"), CloudSyncManager::Error);
308 }
else if (result ==
QLatin1StringView(R
"({"message":"Current user is not logged in"})") && statusCode == 401) {
310 d->m_cloudSyncManager->setStatus(tr(
"Username or password are incorrect"), CloudSyncManager::Error);
311 }
else if (result.
contains(QStringLiteral(R
"("status":"success")")) && statusCode == 200) {
313 d->m_cloudSyncManager->setStatus(tr(
"Login successful"), CloudSyncManager::Success);
317void OwncloudSyncBackend::cancelUpload()
319 d->m_routeUploadReply->abort();
322void OwncloudSyncBackend::prepareRouteList()
327 d->m_routeList.clear();
331 for (
int index = 0; index < dataArray.
size(); ++index) {
332 QJsonObject dataObject = dataArray[index].toObject();
335 route.setIdentifier(dataObject.
value(QStringLiteral(
"timestamp")).
toString());
336 route.setName(dataObject.
value(QStringLiteral(
"name")).
toString());
337 route.setDistance(dataObject.
value(QStringLiteral(
"distance")).
toString());
338 route.setDuration(dataObject.
value(QStringLiteral(
"duration")).
toString());
339 route.setPreviewUrl(endpointUrl(d->m_routePreviewEndpoint, route.identifier()));
340 route.setOnCloud(
true);
342 d->m_routeList.
append(route);
347 if (!d->m_routeList.isEmpty()) {
348 d->m_routeList.remove(d->m_routeList.count() - 1);
351 Q_EMIT routeListDownloaded(d->m_routeList);
354void OwncloudSyncBackend::saveDownloadedRoute()
358 bool pathCreated = d->m_cacheDir.mkpath(d->m_cacheDir.absolutePath());
360 mDebug() <<
"Couldn't create the path " << d->m_cacheDir.absolutePath() <<
". Check if your user has sufficient permissions for this operation.";
363 QString kmlFilePath = QStringLiteral(
"%0/%1.kml").
arg(d->m_cacheDir.absolutePath(), timestamp);
364 QFile kmlFile(kmlFilePath);
368 mDebug() <<
"Failed to open file" << kmlFilePath <<
" for writing."
369 <<
" Its directory either is missing or is not writable.";
373 kmlFile.write(d->m_routeDownloadReply->readAll());
376 QString previewPath = QStringLiteral(
"%0/preview/").
arg(d->m_cacheDir.absolutePath());
377 bool previewPathCreated = d->m_cacheDir.mkpath(previewPath);
378 if (!previewPathCreated) {
379 mDebug() <<
"Couldn't create the path " << previewPath <<
". Check if your user has sufficient permissions for this operation.";
382 QString previewFilePath = QStringLiteral(
"%0/preview/%1.jpg").
arg(d->m_cacheDir.absolutePath(), timestamp);
383 QFile previewFile(previewFilePath);
386 if (!previewFileOpened) {
387 mDebug() <<
"Failed to open file" << previewFilePath <<
"for writing."
388 <<
" Its directory either is missing or is not writable.";
392 QPixmap preview = createPreview(timestamp);
393 preview.
save(&previewFile,
"JPG");
396 Q_EMIT routeDownloaded();
399QUrl OwncloudSyncBackend::endpointUrl(
const QString &endpoint)
const
401 const QString endpointUrl = d->m_cloudSyncManager->apiUrl().toString() +
QLatin1Char(
'/') + endpoint;
402 return QUrl(endpointUrl);
405QUrl OwncloudSyncBackend::endpointUrl(
const QString &endpoint,
const QString ¶meter)
const
408 return QUrl(endpointUrl);
411void OwncloudSyncBackend::removeFromCache(
const QDir &cacheDir,
const QString ×tamp)
414 bool previewRemoved =
QFile(QStringLiteral(
"%0/preview/%1.jpg").arg(cacheDir.
absolutePath(), timestamp)).
remove();
415 if (!fileRemoved || !previewRemoved) {
416 mDebug() <<
"Failed to remove locally cached route " << timestamp
418 "have been removed already, or its directory is missing / not writable.";
421 Q_EMIT removedFromCache(timestamp);
426#include "moc_OwncloudSyncBackend.cpp"
This file contains the headers for MarbleModel.
Q_SCRIPTABLE CaptureState status()
char * toString(const EngineQuery &query)
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
QByteArray & append(QByteArrayView data)
QString absolutePath() const const
bool mkpath(const QString &dirPath) const const
QString fileName() const const
void append(const QJsonValue &value)
qsizetype size() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QJsonValue value(QLatin1StringView key) const const
bool isArray() const const
QJsonArray toArray() const const
void errorOccurred(QNetworkReply::NetworkError code)
bool save(QIODevice *device, const char *format, int quality) const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString left(qsizetype n) const const
qsizetype length() const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime fromString(QStringView string, QStringView format)
int secsTo(QTime t) const const