diff options
-rw-r--r-- | core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | core/uploadDiveLogsDE.cpp | 320 | ||||
-rw-r--r-- | core/uploadDiveLogsDE.h | 36 |
3 files changed, 358 insertions, 0 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a66d87355..eb7185812 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -167,6 +167,8 @@ set(SUBSURFACE_CORE_LIB_SRCS units.c uploadDiveShare.cpp uploadDiveShare.h + uploadDiveLogsDE.cpp + uploadDiveLogsDE.h version.c version.h videoframeextractor.cpp diff --git a/core/uploadDiveLogsDE.cpp b/core/uploadDiveLogsDE.cpp new file mode 100644 index 000000000..2431e17f4 --- /dev/null +++ b/core/uploadDiveLogsDE.cpp @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "uploadDiveLogsDE.h" +#include <QDir> +#include <QDebug> +#include <zip.h> +#include <errno.h> +#include "core/display.h" +#include "core/errorhelper.h" +#include "core/qthelper.h" +#include "core/dive.h" +#include "core/membuffer.h" +#include "core/divesite.h" +#include "core/cloudstorage.h" +#include "core/settings/qPrefCloudStorage.h" + + +uploadDiveLogsDE *uploadDiveLogsDE::instance() +{ + static uploadDiveLogsDE *self = new uploadDiveLogsDE; + + return self; +} + + +uploadDiveLogsDE::uploadDiveLogsDE(): + reply(NULL), + multipart(NULL) +{ + timeout.setSingleShot(true); +} + + +void uploadDiveLogsDE::doUpload(bool selected, const QString &userid, const QString &password) +{ + QString err; + + /* generate a temporary filename and create/open that file with zip_open */ + QString filename(QDir::tempPath() + "/divelogsde-upload.dld"); + + // delete file if it exist + QFile f(filename); + if (f.open(QIODevice::ReadOnly)) { + f.close(); + f.remove(); + } + + // Make zip file, with all dives, in divelogs.de format + if (!prepareDives(selected, filename)) { + err = tr("Failed to create uploadDiveLogsDE file %s\n").arg(filename); + report_error(err.toUtf8()); + emit uploadFinish(false, err); + timeout.stop(); + return; + } + + // And upload it + uploadDives(filename, userid, password); + timeout.stop(); +} + + +bool uploadDiveLogsDE::prepareDives(bool selected, const QString &filename) +{ + static const char errPrefix[] = "divelog.de-uploadDiveLogsDE:"; + xsltStylesheetPtr xslt = NULL; + struct zip *zip; + + xslt = get_stylesheet("divelogs-export.xslt"); + if (!xslt) { + qDebug() << errPrefix << "missing stylesheet"; + report_error(tr("Stylesheet to export to divelogs.de is not found").toUtf8()); + return false; + } + + // Prepare zip file + int error_code; + zip = zip_open(QFile::encodeName(QDir::toNativeSeparators(filename)), ZIP_CREATE, &error_code); + if (!zip) { + char buffer[1024]; + zip_error_to_str(buffer, sizeof buffer, error_code, errno); + report_error(tr("Failed to create zip file for uploadDiveLogsDE: %s").toUtf8(), buffer); + return false; + } + + /* walk the dive list in chronological order */ + int i; + struct dive *dive; + for_each_dive (i, dive) { + char xmlfilename[PATH_MAX]; + int streamsize; + const char *membuf; + xmlDoc *transformed; + struct zip_source *s; + struct membuffer mb = {}; + + /* + * Get the i'th dive in XML format so we can process it. + * We need to save to a file before we can reload it back into memory... + */ + if (selected && !dive->selected) + continue; + + /* make sure the buffer is empty and add the dive */ + struct dive_site *ds = dive->dive_site; + mb.len = 0; + if (ds) { + put_format(&mb, "<divelog><divesites><site uuid='%8x' name='", ds->uuid); + put_quoted(&mb, ds->name, 1, 0); + put_format(&mb, "'"); + put_location(&mb, &ds->location, " gps='", "'"); + put_format(&mb, ">\n"); + if (ds->taxonomy.nr) { + for (int j = 0; j < ds->taxonomy.nr; j++) { + struct taxonomy *t = &ds->taxonomy.category[j]; + if (t->category != TC_NONE && t->category == prefs.geocoding.category[j] && t->value) { + put_format(&mb, " <geo cat='%d'", t->category); + put_format(&mb, " origin='%d' value='", t->origin); + put_quoted(&mb, t->value, 1, 0); + put_format(&mb, "'/>\n"); + } + } + } + put_format(&mb, "</site>\n</divesites>\n"); + } + save_one_dive_to_mb(&mb, dive, false); + + if (ds) { + put_format(&mb, "</divelog>\n"); + } + membuf = mb_cstring(&mb); + streamsize = strlen(membuf); + /* + * Parse the memory buffer into XML document and + * transform it to divelogs.de format, finally dumping + * the XML into a character buffer. + */ + xmlDoc *doc = xmlReadMemory(membuf, streamsize, "divelog", NULL, 0); + if (!doc) { + qWarning() << errPrefix << "could not parse back into memory the XML file we've just created!"; + report_error(tr("internal error").toUtf8()); + zip_close(zip); + QFile::remove(filename); + xsltFreeStylesheet(xslt); + return false; + } + free_buffer(&mb); + + transformed = xsltApplyStylesheet(xslt, doc, NULL); + if (!transformed) { + qWarning() << errPrefix << "XSLT transform failed for dive: " << i; + report_error(tr("Conversion of dive %1 to divelogs.de format failed").arg(i).toUtf8()); + continue; + } + xmlDocDumpMemory(transformed, (xmlChar **)&membuf, &streamsize); + xmlFreeDoc(doc); + xmlFreeDoc(transformed); + + /* + * Save the XML document into a zip file. + */ + snprintf(xmlfilename, PATH_MAX, "%d.xml", i + 1); + s = zip_source_buffer(zip, membuf, streamsize, 1); + if (s) { + int64_t ret = zip_add(zip, xmlfilename, s); + if (ret == -1) { + qDebug() << errPrefix << "failed to include dive:" << i; + } + } + } + xsltFreeStylesheet(xslt); + if (zip_close(zip)) { + int ze, se; +#if LIBZIP_VERSION_MAJOR >= 1 + zip_error_t *error = zip_get_error(zip); + ze = zip_error_code_zip(error); + se = zip_error_code_system(error); +#else + zip_error_get(zip, &ze, &se); +#endif + report_error(qPrintable(tr("error writing zip file: %s zip error %d system error %d - %s")), + qPrintable(QDir::toNativeSeparators(filename)), ze, se, zip_strerror(zip)); + return false; + } + return true; +} + + +void uploadDiveLogsDE::uploadDives(const QString &filename, const QString &userid, const QString &password) +{ + QHttpPart part1, part2, part3; + static QNetworkRequest request; + QString args; + + // Check if there is an earlier request open + if (reply != NULL && reply->isOpen()) { + reply->abort(); + delete reply; + reply = NULL; + } + if (multipart != NULL) { + delete multipart; + multipart = NULL; + } + multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + // prepare header with filename (of all dives) and pointer to file + args = "form-data; name=\"userfile\"; filename=\"" + filename + "\""; + part1.setRawHeader("Content-Disposition", args.toLatin1()); + QFile *f = new QFile(filename); + if (!f->open(QIODevice::ReadOnly)) { + qDebug() << "ERROR opening zip file: " << filename; + return; + } + part1.setBodyDevice(f); + multipart->append(part1); + + // Add userid + args = "form-data; name=\"user\""; + part2.setRawHeader("Content-Disposition", args.toLatin1()); + part2.setBody(qPrefCloudStorage::divelogde_user().toUtf8()); + multipart->append(part2); + + // Add password + args = "form-data; name=\"pass\""; + part3.setRawHeader("Content-Disposition", args.toLatin1()); + part3.setBody(qPrefCloudStorage::divelogde_pass().toUtf8()); + multipart->append(part3); + + // Prepare network request + request.setUrl(QUrl("https://divelogs.de/DivelogsDirectImport.php")); + request.setRawHeader("Accept", "text/xml, application/xml"); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + + // Execute async. + reply = manager()->post(request, multipart); + + // connect signals from upload process + connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, + SLOT(uploadError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, + SLOT(updateProgress(qint64, qint64))); + connect(&timeout, SIGNAL(timeout()), this, SLOT(uploadTimeout())); + + timeout.start(30000); // 30s +} + + +void uploadDiveLogsDE::updateProgress(qint64 current, qint64 total) +{ + if (!reply) + return; + if (total <= 0 || current <= 0) + return; + + // Calculate percentage + // And signal whoever wants to know + qreal percentage = (float)current / (float)total; + emit uploadProgress(percentage); + + // reset the timer: 30 seconds after we last got any data + timeout.start(); +} + + +void uploadDiveLogsDE::uploadFinished() +{ + QString err; + + if (!reply) + return; + + // check what the server sent us: it might contain + // an error condition, such as a failed login + QByteArray xmlData = reply->readAll(); + reply->deleteLater(); + reply = NULL; + char *resp = xmlData.data(); + if (resp) { + char *parsed = strstr(resp, "<Login>"); + if (parsed) { + if (strstr(resp, "<Login>succeeded</Login>")) { + if (strstr(resp, "<FileCopy>failed</FileCopy>")) { + report_error(tr("Upload failed").toUtf8()); + return; + } + err = tr("Upload successful"); + emit uploadFinish(true, err); + return; + } + err = tr("Login failed"); + report_error(err.toUtf8()); + emit uploadFinish(false, err); + timeout.stop(); + return; + } + err = tr("Cannot parse response"); + report_error(tr("Cannot parse response").toUtf8()); + emit uploadFinish(false, err); + timeout.stop(); + } +} + + +void uploadDiveLogsDE::uploadTimeout() +{ + QString err(tr("divelogs.de not responding")); + report_error(err.toUtf8()); + emit uploadFinish(false, err); + timeout.stop(); +} + + +void uploadDiveLogsDE::uploadError(QNetworkReply::NetworkError error) +{ + QString err(tr("network error %1").arg(error)); + report_error(err.toUtf8()); + emit uploadFinish(false, err); + timeout.stop(); +} diff --git a/core/uploadDiveLogsDE.h b/core/uploadDiveLogsDE.h new file mode 100644 index 000000000..d19d20a26 --- /dev/null +++ b/core/uploadDiveLogsDE.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef UPLOADDIVELOGSDE_H +#define UPLOADDIVELOGSDE_H +#include <QNetworkReply> +#include <QHttpMultiPart> +#include <QTimer> + + +class uploadDiveLogsDE : public QObject { + Q_OBJECT + +public: + static uploadDiveLogsDE *instance(); + void doUpload(bool selected, const QString &userid, const QString &password); + +private slots: + void updateProgress(qint64 current, qint64 total); + void uploadFinished(); + void uploadTimeout(); + void uploadError(QNetworkReply::NetworkError error); + +signals: + void uploadFinish(bool success, const QString &text); + void uploadProgress(qreal percentage); + +private: + uploadDiveLogsDE(); + + bool prepareDives(bool selected, const QString &filename); + void uploadDives(const QString &filename, const QString &userid, const QString &password); + + QNetworkReply *reply; + QHttpMultiPart *multipart; + QTimer timeout; +}; +#endif // UPLOADDIVELOGSDE_H |