From 0ed95678bac95fce37c1e754367aef1fc5c27bc7 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 14:39:17 -0800 Subject: Whitespace: fix indentation in subsurfacewebservices.* to use tabs Signed-off-by: Thiago Macieira --- qt-ui/subsurfacewebservices.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 515e3fe9c..83aba372b 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -13,7 +13,7 @@ class QNetworkReply; class WebServices : public QDialog{ Q_OBJECT public: - explicit WebServices(QWidget* parent = 0, Qt::WindowFlags f = 0); + explicit WebServices(QWidget* parent = 0, Qt::WindowFlags f = 0); void hidePassword(); void hideUpload(); @@ -41,7 +41,7 @@ private slots: void downloadError(QNetworkReply::NetworkError error); void startUpload(){} /*no op*/ private: - explicit SubsurfaceWebServices(QWidget* parent = 0, Qt::WindowFlags f = 0); + explicit SubsurfaceWebServices(QWidget* parent = 0, Qt::WindowFlags f = 0); void setStatusText(int status); void download_dialog_traverse_xml(xmlNodePtr node, unsigned int *download_status); unsigned int download_dialog_parse_response(const QByteArray& length); @@ -57,9 +57,9 @@ private slots: void buttonClicked(QAbstractButton* button); void downloadFinished(); void downloadError(QNetworkReply::NetworkError error); - void startUpload(); + void startUpload(); private: - explicit DivelogsDeWebServices (QWidget* parent = 0, Qt::WindowFlags f = 0); + explicit DivelogsDeWebServices (QWidget* parent = 0, Qt::WindowFlags f = 0); void setStatusText(int status); void download_dialog_traverse_xml(xmlNodePtr node, unsigned int *download_status); unsigned int download_dialog_parse_response(const QByteArray& length); -- cgit v1.2.3-70-g09d2 From ff96bcb0fc9ef47c94fee8e675431882d20ba9dc Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 14:55:49 -0800 Subject: Make the QNetworkAccessManager a singleton available to all One of the rules of using QNetworkAccessManager is to share it among all users, since sockets and other state can be shared. Looks like Marble doesn't allow us to set it, though, and it creates multiple instances. I'll prepare an upstream patch to fix that sometime. Signed-off-by: Thiago Macieira --- qt-ui/subsurfacewebservices.cpp | 16 ++++++++-------- qt-ui/subsurfacewebservices.h | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 5edc5cb02..4713ef2af 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -23,7 +23,6 @@ WebServices::WebServices(QWidget* parent, Qt::WindowFlags f): QDialog(parent, f) connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*))); connect(ui.download, SIGNAL(clicked(bool)), this, SLOT(startDownload())); ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); - } void WebServices::hidePassword() @@ -37,6 +36,12 @@ void WebServices::hideUpload() ui.upload->hide(); } +QNetworkAccessManager *WebServices::manager() +{ + static QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + return manager; +} + // # // # // # Subsurface Web Service Implementation. @@ -89,9 +94,7 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton* button) } break; case QDialogButtonBox::RejectRole: - // we may want to clean up after ourselves, but this - // makes Subsurface throw a SIGSEGV... - // manager->deleteLater(); + // we may want to clean up after ourselves // reply->deleteLater(); ui.progressBar->setMaximum(1); break; @@ -108,11 +111,10 @@ void SubsurfaceWebServices::startDownload() QUrl url("http://api.hohndel.org/api/dive/get/"); url.addQueryItem("login", ui.userID->text().toUpper()); - manager = new QNetworkAccessManager(this); QNetworkRequest request; request.setUrl(url); request.setRawHeader("Accept", "text/xml"); - reply = manager->get(request); + reply = manager()->get(request); ui.status->setText(tr("Wait a bit until we have something...")); ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin' ui.download->setEnabled(false); @@ -136,7 +138,6 @@ void SubsurfaceWebServices::downloadFinished() if (resultCode == DD_STATUS_OK){ ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); } - manager->deleteLater(); reply->deleteLater(); } @@ -145,7 +146,6 @@ void SubsurfaceWebServices::downloadError(QNetworkReply::NetworkError) ui.download->setEnabled(true); ui.progressBar->setRange(0,1); ui.status->setText(QString::number((int)QNetworkRequest::HttpStatusCodeAttribute)); - manager->deleteLater(); reply->deleteLater(); } diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 83aba372b..0400fd503 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -17,6 +17,8 @@ public: void hidePassword(); void hideUpload(); + static QNetworkAccessManager *manager(); + private slots: virtual void startDownload() = 0; virtual void startUpload() = 0; @@ -25,7 +27,6 @@ private slots: protected: Ui::WebServices ui; QNetworkReply *reply; - QNetworkAccessManager *manager; QByteArray downloadedData; }; -- cgit v1.2.3-70-g09d2 From fa07f554e2894c7720a41c1fbb379743dd6c7285 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 16:50:46 -0800 Subject: Properly reset the state of the download dialog before dismissing Re-enable buttons that should be enabled by default, disable the others, set the status to empty, make the progress bar go back to zero. Signed-off-by: Thiago Macieira --- qt-ui/subsurfacewebservices.cpp | 16 ++++++++++++---- qt-ui/subsurfacewebservices.h | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 850e076f3..1f47d965b 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -115,6 +115,14 @@ QNetworkAccessManager *WebServices::manager() return manager; } +void WebServices::resetState() +{ + ui.download->setEnabled(true); + ui.progressBar->reset(); + ui.progressBar->setRange(0,1); + ui.status->setText(QString()); +} + // # // # // # Subsurface Web Service Implementation. @@ -156,12 +164,13 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton* button) s.sync(); hide(); close(); + resetState(); } break; case QDialogButtonBox::RejectRole: // we may want to clean up after ourselves // reply->deleteLater(); - ui.progressBar->setMaximum(1); + resetState(); break; case QDialogButtonBox::HelpRole: QDesktopServices::openUrl(QUrl("http://api.hohndel.org")); @@ -208,9 +217,8 @@ void SubsurfaceWebServices::downloadFinished() void SubsurfaceWebServices::downloadError(QNetworkReply::NetworkError) { - ui.download->setEnabled(true); - ui.progressBar->setRange(0,1); - ui.status->setText(QString::number((int)QNetworkRequest::HttpStatusCodeAttribute)); + resetState(); + ui.status->setText(tr("Download error: %1").arg(reply->errorString())); reply->deleteLater(); } diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 0400fd503..0cd083001 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -25,6 +25,8 @@ private slots: virtual void buttonClicked(QAbstractButton* button) = 0; protected: + void resetState(); + Ui::WebServices ui; QNetworkReply *reply; QByteArray downloadedData; -- cgit v1.2.3-70-g09d2 From ab1b314a84347b648fa0df6a9719f67a9fb54d54 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 16:52:08 -0800 Subject: Set the download reply pointer to NULL after dismissing it QNetworkReply might emit signals after it's been told to go away. We don't want to change the status after that. Signed-off-by: Thiago Macieira --- qt-ui/subsurfacewebservices.cpp | 42 +++++++++++++++++++++++++++++++++++++---- qt-ui/subsurfacewebservices.h | 4 ++++ 2 files changed, 42 insertions(+), 4 deletions(-) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 1f47d965b..2607dbb2e 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -115,6 +115,37 @@ QNetworkAccessManager *WebServices::manager() return manager; } +void WebServices::updateProgress(qint64 current, qint64 total) +{ + if (!reply) + return; + + if (total >= INT_MAX / 2) { + // over a gigabyte! + if (total >= Q_INT64_C(1) << 47) { + total >>= 16; + current >>= 16; + } + total >>= 16; + current >>= 16; + } + ui.progressBar->setRange(0, total); + ui.progressBar->setValue(current); + ui.status->setText(tr("Downloading...")); + + // reset the timer: 30 seconds after we last got any data + timeout.start(); +} + +void WebServices::connectSignalsForDownload(QNetworkReply *reply) +{ + connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, + SLOT(updateProgress(qint64,qint64))); +} + void WebServices::resetState() { ui.download->setEnabled(true); @@ -170,6 +201,7 @@ void SubsurfaceWebServices::buttonClicked(QAbstractButton* button) case QDialogButtonBox::RejectRole: // we may want to clean up after ourselves // reply->deleteLater(); + reply = NULL; resetState(); break; case QDialogButtonBox::HelpRole: @@ -193,14 +225,14 @@ void SubsurfaceWebServices::startDownload() ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin' ui.download->setEnabled(false); ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); - - connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(downloadError(QNetworkReply::NetworkError))); + connectSignalsForDownload(reply); } void SubsurfaceWebServices::downloadFinished() { + if (!reply) + return; + ui.progressBar->setRange(0,1); downloadedData = reply->readAll(); @@ -213,6 +245,7 @@ void SubsurfaceWebServices::downloadFinished() ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); } reply->deleteLater(); + reply = NULL; } void SubsurfaceWebServices::downloadError(QNetworkReply::NetworkError) @@ -220,6 +253,7 @@ void SubsurfaceWebServices::downloadError(QNetworkReply::NetworkError) resetState(); ui.status->setText(tr("Download error: %1").arg(reply->errorString())); reply->deleteLater(); + reply = NULL; } void SubsurfaceWebServices::setStatusText(int status) diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 0cd083001..05735f859 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -24,8 +24,12 @@ private slots: virtual void startUpload() = 0; virtual void buttonClicked(QAbstractButton* button) = 0; +protected slots: + void updateProgress(qint64 current, qint64 total); + protected: void resetState(); + void connectSignalsForDownload(QNetworkReply *reply); Ui::WebServices ui; QNetworkReply *reply; -- cgit v1.2.3-70-g09d2 From 919c7045b76f61a67cd00189127cd7d3488065b0 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 17:47:35 -0800 Subject: Add support for timing the download out The time out is 30 seconds from the start of the request or from the last time we got any data from the server. Signed-off-by: Thiago Macieira --- qt-ui/subsurfacewebservices.cpp | 15 +++++++++++++++ qt-ui/subsurfacewebservices.h | 3 +++ 2 files changed, 18 insertions(+) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 2607dbb2e..5756e806d 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -95,7 +95,9 @@ WebServices::WebServices(QWidget* parent, Qt::WindowFlags f): QDialog(parent, f) ui.setupUi(this); connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*))); connect(ui.download, SIGNAL(clicked(bool)), this, SLOT(startDownload())); + connect(&timeout, SIGNAL(timeout()), this, SLOT(downloadTimedOut())); ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + timeout.setSingleShot(true); } void WebServices::hidePassword() @@ -115,6 +117,17 @@ QNetworkAccessManager *WebServices::manager() return manager; } +void WebServices::downloadTimedOut() +{ + if (!reply) + return; + + reply->deleteLater(); + reply = NULL; + resetState(); + ui.status->setText(tr("Download timed out")); +} + void WebServices::updateProgress(qint64 current, qint64 total) { if (!reply) @@ -144,6 +157,8 @@ void WebServices::connectSignalsForDownload(QNetworkReply *reply) this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateProgress(qint64,qint64))); + + timeout.start(30000); // 30s } void WebServices::resetState() diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 05735f859..89c41e4dd 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "ui_webservices.h" @@ -23,6 +24,7 @@ private slots: virtual void startDownload() = 0; virtual void startUpload() = 0; virtual void buttonClicked(QAbstractButton* button) = 0; + virtual void downloadTimedOut(); protected slots: void updateProgress(qint64 current, qint64 total); @@ -33,6 +35,7 @@ protected: Ui::WebServices ui; QNetworkReply *reply; + QTimer timeout; QByteArray downloadedData; }; -- cgit v1.2.3-70-g09d2 From a1972bc3433d2e412147a6925787ffee66adcb4b Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 14 Nov 2013 18:57:09 -0800 Subject: Implement the network part of the support for divelogs.de This implements support for: * uploading a zip file containing dives - untested (the zip file must have been prepared elsewhere) * downloading the dive list and the dive XML files The networking part is finished, but it's missing the actual import of the XML files sent by divelogs.de. Signed-off-by: Thiago Macieira --- qt-ui/mainwindow.cpp | 2 +- qt-ui/subsurfacewebservices.cpp | 318 +++++++++++++++++++++++++++++++++++++++- qt-ui/subsurfacewebservices.h | 13 ++ 3 files changed, 327 insertions(+), 6 deletions(-) (limited to 'qt-ui/subsurfacewebservices.h') diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 101219725..b216240fa 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -269,7 +269,7 @@ void MainWindow::on_actionDownloadWeb_triggered() void MainWindow::on_actionDivelogs_de_triggered() { - DivelogsDeWebServices::instance()->exec(); + DivelogsDeWebServices::instance()->downloadDives(); } void MainWindow::on_actionEditDeviceNames_triggered() diff --git a/qt-ui/subsurfacewebservices.cpp b/qt-ui/subsurfacewebservices.cpp index 5756e806d..6a6da1853 100644 --- a/qt-ui/subsurfacewebservices.cpp +++ b/qt-ui/subsurfacewebservices.cpp @@ -3,16 +3,25 @@ #include "mainwindow.h" #include +#include +#include +#include +#include #include #include #include #include +#include #include #include "../dive.h" #include "../divelist.h" +#ifdef Q_OS_UNIX +# include // for dup(2) +#endif + struct dive_table gps_location_table; static bool merge_locations_into_dives(void); @@ -95,6 +104,7 @@ WebServices::WebServices(QWidget* parent, Qt::WindowFlags f): QDialog(parent, f) ui.setupUi(this); connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*))); connect(ui.download, SIGNAL(clicked(bool)), this, SLOT(startDownload())); + connect(ui.upload, SIGNAL(clicked(bool)), this, SLOT(startUpload())); connect(&timeout, SIGNAL(timeout()), this, SLOT(downloadTimedOut())); ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); timeout.setSingleShot(true); @@ -109,6 +119,13 @@ void WebServices::hidePassword() void WebServices::hideUpload() { ui.upload->hide(); + ui.download->show(); +} + +void WebServices::hideDownload() +{ + ui.download->hide(); + ui.upload->show(); } QNetworkAccessManager *WebServices::manager() @@ -125,7 +142,7 @@ void WebServices::downloadTimedOut() reply->deleteLater(); reply = NULL; resetState(); - ui.status->setText(tr("Download timed out")); + ui.status->setText(tr("Operation timed out")); } void WebServices::updateProgress(qint64 current, qint64 total) @@ -144,7 +161,7 @@ void WebServices::updateProgress(qint64 current, qint64 total) } ui.progressBar->setRange(0, total); ui.progressBar->setValue(current); - ui.status->setText(tr("Downloading...")); + ui.status->setText(tr("Transfering data...")); // reset the timer: 30 seconds after we last got any data timeout.start(); @@ -164,6 +181,9 @@ void WebServices::connectSignalsForDownload(QNetworkReply *reply) void WebServices::resetState() { ui.download->setEnabled(true); + ui.upload->setEnabled(true); + ui.userID->setEnabled(true); + ui.password->setEnabled(true); ui.progressBar->reset(); ui.progressBar->setRange(0,1); ui.status->setText(QString()); @@ -252,7 +272,7 @@ void SubsurfaceWebServices::downloadFinished() downloadedData = reply->readAll(); ui.download->setEnabled(true); - ui.status->setText(tr("Download Finished")); + ui.status->setText(tr("Download finished")); uint resultCode = download_dialog_parse_response(downloadedData); setStatusText(resultCode); @@ -325,6 +345,92 @@ end: // # // # +struct DiveListResult +{ + QString errorCondition; + QString errorDetails; + QByteArray idList; // comma-separated, suitable to be sent in the fetch request + int idCount; +}; + +static DiveListResult parseDiveLogsDeDiveList(const QByteArray &xmlData) +{ + /* XML format seems to be: + * + * + * DD.MM.YYYY hh:mm + * [repeat ] + * + * + */ + QXmlStreamReader reader(xmlData); + const QString invalidXmlError = DivelogsDeWebServices::tr("Invalid response from server"); + bool seenDiveDates = false; + DiveListResult result; + result.idCount = 0; + + if (reader.readNextStartElement() && reader.name() != "DiveDateReader") { + result.errorCondition = invalidXmlError; + result.errorDetails = + DivelogsDeWebServices::tr("Expected XML tag 'DiveDateReader', got instead '%1") + .arg(reader.name().toString()); + goto out; + } + + while (reader.readNextStartElement()) { + if (reader.name() != "DiveDates") { + if (reader.name() == "Login") { + QString status = reader.readElementText(); + // qDebug() << "Login status:" << status; + + // Note: there has to be a better way to determine a successful login... + if (status == "failed") { + result.errorCondition = "Login failed"; + goto out; + } + } else { + // qDebug() << "Skipping" << reader.name(); + } + continue; + } + + // process + seenDiveDates = true; + while (reader.readNextStartElement()) { + if (reader.name() != "date") { + // qDebug() << "Skipping" << reader.name(); + continue; + } + QStringRef id = reader.attributes().value("divelogsId"); + // qDebug() << "Found" << reader.name() << "with id =" << id; + if (!id.isEmpty()) { + result.idList += id.toLatin1(); + result.idList += ','; + ++result.idCount; + } + + reader.skipCurrentElement(); + } + } + + // chop the ending comma, if any + result.idList.chop(1); + + if (!seenDiveDates) { + result.errorCondition = invalidXmlError; + result.errorDetails = DivelogsDeWebServices::tr("Expected XML tag 'DiveDates' not found"); + } + +out: + if (reader.hasError()) { + // if there was an XML error, overwrite the result or other error conditions + result.errorCondition = invalidXmlError; + result.errorDetails = DivelogsDeWebServices::tr("Malformed XML response. Line %1: %2") + .arg(reader.lineNumber()).arg(reader.errorString()); + } + return result; +} + DivelogsDeWebServices* DivelogsDeWebServices::instance() { static DivelogsDeWebServices *self = new DivelogsDeWebServices(mainWindow()); @@ -332,24 +438,217 @@ DivelogsDeWebServices* DivelogsDeWebServices::instance() return self; } -DivelogsDeWebServices::DivelogsDeWebServices(QWidget* parent, Qt::WindowFlags f): WebServices(parent, f) +void DivelogsDeWebServices::downloadDives() { + hideUpload(); + exec(); +} +void DivelogsDeWebServices::uploadDives(QIODevice *dldContent) +{ + QHttpMultiPart mp(QHttpMultiPart::FormDataType); + QHttpPart part; + part.setRawHeader("Content-Disposition", "form-data; name=\"userfile\""); + part.setBodyDevice(dldContent); + mp.append(part); + + multipart = ∓ + hideDownload(); + exec(); + multipart = NULL; + + delete reply; // we need to ensure it has stopped using our QHttpMultiPart +} + +DivelogsDeWebServices::DivelogsDeWebServices(QWidget* parent, Qt::WindowFlags f): WebServices(parent, f) +{ + QSettings s; + ui.userID->setText(s.value("divelogde_user").toString()); + ui.password->setText(s.value("divelogde_pass").toString()); + hideUpload(); } void DivelogsDeWebServices::startUpload() { + ui.status->setText(tr("Uploading dive list...")); + ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin' + ui.upload->setEnabled(false); + ui.userID->setEnabled(false); + ui.password->setEnabled(false); + QNetworkRequest request; + request.setUrl(QUrl("https://divelogs.de/DivelogsDirectImport.php")); + request.setRawHeader("Accept", "text/xml, application/xml"); + + QHttpPart part; + part.setRawHeader("Content-Disposition", "form-data; name=\"user\""); + part.setBody(ui.userID->text().toUtf8()); + multipart->append(part); + + part.setRawHeader("Content-Disposition", "form-data; name=\"pass\""); + part.setBody(ui.password->text().toUtf8()); + multipart->append(part); + + reply = manager()->post(request, multipart); + 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))); + + timeout.start(30000); // 30s } void DivelogsDeWebServices::startDownload() { + ui.status->setText(tr("Downloading dive list...")); + ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin' + ui.download->setEnabled(false); + ui.userID->setEnabled(false); + ui.password->setEnabled(false); + + QNetworkRequest request; + request.setUrl(QUrl("https://divelogs.de/xml_available_dives.php")); + request.setRawHeader("Accept", "text/xml, application/xml"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QUrl body; + body.addQueryItem("user", ui.userID->text()); + body.addQueryItem("pass", ui.password->text()); + + reply = manager()->post(request, body.encodedQuery()); +#else + QUrlQuery body; + body.addQueryItem("user", ui.userID->text()); + body.addQueryItem("pass", ui.password->text()); + + reply = manager()->post(request, body.query(QUrl::FullyEncoded).toLatin1()) +#endif + connect(reply, SIGNAL(finished()), this, SLOT(listDownloadFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(downloadError(QNetworkReply::NetworkError))); + + timeout.start(30000); // 30s +} + +void DivelogsDeWebServices::listDownloadFinished() +{ + if (!reply) + return; + QByteArray xmlData = reply->readAll(); + reply->deleteLater(); + reply = NULL; + + // parse the XML data we downloaded + DiveListResult diveList = parseDiveLogsDeDiveList(xmlData); + if (!diveList.errorCondition.isEmpty()) { + // error condition + resetState(); + ui.status->setText(diveList.errorCondition); + return; + } + + ui.status->setText(tr("Downloading %1 dives...").arg(diveList.idCount)); + + QNetworkRequest request; +// request.setUrl(QUrl("https://divelogs.de/DivelogsDirectExport.php")); + request.setUrl(QUrl("http://divelogs.de/DivelogsDirectExport.php")); + request.setRawHeader("Accept", "application/zip, */*"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + QUrl body; + body.addQueryItem("user", ui.userID->text()); + body.addQueryItem("pass", ui.password->text()); + body.addQueryItem("ids", diveList.idList); + + reply = manager()->post(request, body.encodedQuery()); +#else + QUrlQuery body; + body.addQueryItem("user", ui.userID->text()); + body.addQueryItem("pass", ui.password->text()); + body.addQueryItem("ids", diveList.idList); + + reply = manager()->post(request, body.query(QUrl::FullyEncoded).toLatin1()) +#endif + + connect(reply, SIGNAL(readyRead()), this, SLOT(saveToZipFile())); + connectSignalsForDownload(reply); +} + +void DivelogsDeWebServices::saveToZipFile() +{ + if (!zipFile.isOpen()) { + zipFile.setFileTemplate(QDir::tempPath() + "/import-XXXXXX.dld"); + zipFile.open(); + } + zipFile.write(reply->readAll()); } void DivelogsDeWebServices::downloadFinished() { + if (!reply) + return; + + ui.download->setEnabled(true); + ui.status->setText(tr("Download finished - %1").arg(reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString())); + reply->deleteLater(); + reply = NULL; + + int errorcode; + zipFile.seek(0); +#ifdef Q_OS_UNIX + int duppedfd = dup(zipFile.handle()); + struct zip *zip = zip_fdopen(duppedfd, 0, &errorcode); + if (!zip) + ::close(duppedfd); +#else + struct zip *zip = zip_open(zipFile.fileName(), 0, &errorcode); +#endif + if (!zip) { + char buf[512]; + zip_error_to_str(buf, sizeof(buf), errorcode, errno); + QMessageBox::critical(this, tr("Corrupted download"), + tr("The archive could not be opened:\n%1").arg(QString::fromLocal8Bit(buf))); + zipFile.close(); + return; + } + quint64 entries = zip_get_num_entries(zip, 0); + for (quint64 i = 0; i < entries; ++i) { + struct zip_file *zip_file = zip_fopen_index(zip, i, 0); + if (!zip_file) { + QMessageBox::critical(this, tr("Corrupted download"), + tr("The archive contains corrupt data:\n%1").arg(QString::fromLocal8Bit(zip_strerror(zip)))); + goto close_zip; + } + + // ### FIXME: What do I do with this? + + zip_fclose(zip_file); + } + +close_zip: + zip_close(zip); + zipFile.close(); +} + +void DivelogsDeWebServices::uploadFinished() +{ + if (!reply) + return; + + ui.progressBar->setRange(0,1); + ui.upload->setEnabled(true); + ui.status->setText(tr("Upload finished")); + + // check what the server sent us: it might contain + // an error condition, such as a failed login + QByteArray xmlData = reply->readAll(); + + // ### FIXME: what's the format? } void DivelogsDeWebServices::setStatusText(int status) @@ -357,12 +656,21 @@ void DivelogsDeWebServices::setStatusText(int status) } -void DivelogsDeWebServices::downloadError(QNetworkReply::NetworkError error) +void DivelogsDeWebServices::downloadError(QNetworkReply::NetworkError) { + resetState(); + ui.status->setText(tr("Download error: %1").arg(reply->errorString())); + reply->deleteLater(); + reply = NULL; +} +void DivelogsDeWebServices::uploadError(QNetworkReply::NetworkError error) +{ + downloadError(error); } void DivelogsDeWebServices::buttonClicked(QAbstractButton* button) { } + diff --git a/qt-ui/subsurfacewebservices.h b/qt-ui/subsurfacewebservices.h index 89c41e4dd..3c41b08d1 100644 --- a/qt-ui/subsurfacewebservices.h +++ b/qt-ui/subsurfacewebservices.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,6 +11,7 @@ class QAbstractButton; class QNetworkReply; +class QHttpMultiPart; class WebServices : public QDialog{ Q_OBJECT @@ -17,6 +19,7 @@ public: explicit WebServices(QWidget* parent = 0, Qt::WindowFlags f = 0); void hidePassword(); void hideUpload(); + void hideDownload(); static QNetworkAccessManager *manager(); @@ -32,6 +35,7 @@ protected slots: protected: void resetState(); void connectSignalsForDownload(QNetworkReply *reply); + void connectSignalsForUpload(); Ui::WebServices ui; QNetworkReply *reply; @@ -61,18 +65,27 @@ class DivelogsDeWebServices : public WebServices { Q_OBJECT public: static DivelogsDeWebServices * instance(); + void downloadDives(); + void uploadDives(QIODevice *dldContent); private slots: void startDownload(); void buttonClicked(QAbstractButton* button); + void saveToZipFile(); + void listDownloadFinished(); void downloadFinished(); + void uploadFinished(); void downloadError(QNetworkReply::NetworkError error); + void uploadError(QNetworkReply::NetworkError error); void startUpload(); private: explicit DivelogsDeWebServices (QWidget* parent = 0, Qt::WindowFlags f = 0); void setStatusText(int status); void download_dialog_traverse_xml(xmlNodePtr node, unsigned int *download_status); unsigned int download_dialog_parse_response(const QByteArray& length); + + QHttpMultiPart *multipart; + QTemporaryFile zipFile; }; #endif -- cgit v1.2.3-70-g09d2