Changeset a1972bc in subsurface


Ignore:
Timestamp:
Dec 3, 2013, 1:53:00 PM (4 years ago)
Author:
Thiago Macieira <thiago@…>
Branches:
Mtest, android_test, dcDownload, foratdotde, gitMerge, gitdebug, gitpictures, ios, master, mergeKirigamiPort, pictimeshift, resolutionIndependence, spinner, testTomaz, testing, v4.0-branch, v4.0.3-branch, v4.2-branch, v4.4-branch, v4.5-branch
Children:
662e642a
Parents:
bffb384
git-author:
Thiago Macieira <thiago@…> (11/14/13 18:57:09)
git-committer:
Thiago Macieira <thiago@…> (12/03/13 13:53:00)
Message:

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 <thiago@…>

Location:
qt-ui
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • qt-ui/mainwindow.cpp

    r8189790 ra1972bc  
    270270void MainWindow::on_actionDivelogs_de_triggered()
    271271{
    272         DivelogsDeWebServices::instance()->exec();
     272        DivelogsDeWebServices::instance()->downloadDives();
    273273}
    274274
  • qt-ui/subsurfacewebservices.cpp

    r919c7045 ra1972bc  
    44
    55#include <libxml/parser.h>
    6 
     6#include <zip.h>
     7
     8#include <QDir>
     9#include <QHttpMultiPart>
     10#include <QMessageBox>
    711#include <QNetworkAccessManager>
    812#include <QNetworkReply>
    913#include <QDebug>
    1014#include <QSettings>
     15#include <QXmlStreamReader>
    1116#include <qdesktopservices.h>
    1217
    1318#include "../dive.h"
    1419#include "../divelist.h"
     20
     21#ifdef Q_OS_UNIX
     22#  include <unistd.h> // for dup(2)
     23#endif
    1524
    1625struct dive_table gps_location_table;
     
    96105        connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonClicked(QAbstractButton*)));
    97106        connect(ui.download, SIGNAL(clicked(bool)), this, SLOT(startDownload()));
     107        connect(ui.upload, SIGNAL(clicked(bool)), this, SLOT(startUpload()));
    98108        connect(&timeout, SIGNAL(timeout()), this, SLOT(downloadTimedOut()));
    99109        ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
     
    110120{
    111121        ui.upload->hide();
     122        ui.download->show();
     123}
     124
     125void WebServices::hideDownload()
     126{
     127        ui.download->hide();
     128        ui.upload->show();
    112129}
    113130
     
    126143        reply = NULL;
    127144        resetState();
    128         ui.status->setText(tr("Download timed out"));
     145        ui.status->setText(tr("Operation timed out"));
    129146}
    130147
     
    145162        ui.progressBar->setRange(0, total);
    146163        ui.progressBar->setValue(current);
    147         ui.status->setText(tr("Downloading..."));
     164        ui.status->setText(tr("Transfering data..."));
    148165
    149166        // reset the timer: 30 seconds after we last got any data
     
    165182{
    166183        ui.download->setEnabled(true);
     184        ui.upload->setEnabled(true);
     185        ui.userID->setEnabled(true);
     186        ui.password->setEnabled(true);
    167187        ui.progressBar->reset();
    168188        ui.progressBar->setRange(0,1);
     
    253273
    254274        ui.download->setEnabled(true);
    255         ui.status->setText(tr("Download Finished"));
     275        ui.status->setText(tr("Download finished"));
    256276
    257277        uint resultCode = download_dialog_parse_response(downloadedData);
     
    326346// #
    327347
     348struct DiveListResult
     349{
     350        QString errorCondition;
     351        QString errorDetails;
     352        QByteArray idList; // comma-separated, suitable to be sent in the fetch request
     353        int idCount;
     354};
     355
     356static DiveListResult parseDiveLogsDeDiveList(const QByteArray &xmlData)
     357{
     358    /* XML format seems to be:
     359     * <DiveDateReader version="1.0">
     360     *   <DiveDates>
     361     *     <date diveLogsId="nnn" lastModified="YYYY-MM-DD hh:mm:ss">DD.MM.YYYY hh:mm</date>
     362     *     [repeat <date></date>]
     363     *   </DiveDates>
     364     * </DiveDateReader>
     365     */
     366    QXmlStreamReader reader(xmlData);
     367    const QString invalidXmlError = DivelogsDeWebServices::tr("Invalid response from server");
     368    bool seenDiveDates = false;
     369    DiveListResult result;
     370    result.idCount = 0;
     371
     372    if (reader.readNextStartElement() && reader.name() != "DiveDateReader") {
     373            result.errorCondition = invalidXmlError;
     374            result.errorDetails =
     375                            DivelogsDeWebServices::tr("Expected XML tag 'DiveDateReader', got instead '%1")
     376                            .arg(reader.name().toString());
     377            goto out;
     378    }
     379
     380    while (reader.readNextStartElement()) {
     381            if (reader.name() != "DiveDates") {
     382                    if (reader.name() == "Login") {
     383                            QString status = reader.readElementText();
     384                            // qDebug() << "Login status:" << status;
     385
     386                            // Note: there has to be a better way to determine a successful login...
     387                            if (status == "failed") {
     388                                    result.errorCondition = "Login failed";
     389                                    goto out;
     390                            }
     391                    } else {
     392                            // qDebug() << "Skipping" << reader.name();
     393                    }
     394                    continue;
     395            }
     396
     397            // process <DiveDates>
     398            seenDiveDates = true;
     399            while (reader.readNextStartElement()) {
     400                    if (reader.name() != "date") {
     401                            // qDebug() << "Skipping" << reader.name();
     402                            continue;
     403                    }
     404                    QStringRef id = reader.attributes().value("divelogsId");
     405                    // qDebug() << "Found" << reader.name() << "with id =" << id;
     406                    if (!id.isEmpty()) {
     407                            result.idList += id.toLatin1();
     408                            result.idList += ',';
     409                            ++result.idCount;
     410                    }
     411
     412                    reader.skipCurrentElement();
     413            }
     414    }
     415
     416    // chop the ending comma, if any
     417    result.idList.chop(1);
     418
     419    if (!seenDiveDates) {
     420            result.errorCondition = invalidXmlError;
     421            result.errorDetails = DivelogsDeWebServices::tr("Expected XML tag 'DiveDates' not found");
     422    }
     423
     424out:
     425    if (reader.hasError()) {
     426            // if there was an XML error, overwrite the result or other error conditions
     427            result.errorCondition = invalidXmlError;
     428            result.errorDetails = DivelogsDeWebServices::tr("Malformed XML response. Line %1: %2")
     429                                  .arg(reader.lineNumber()).arg(reader.errorString());
     430    }
     431    return result;
     432}
     433
    328434DivelogsDeWebServices* DivelogsDeWebServices::instance()
    329435{
     
    333439}
    334440
     441void DivelogsDeWebServices::downloadDives()
     442{
     443        hideUpload();
     444        exec();
     445}
     446
     447void DivelogsDeWebServices::uploadDives(QIODevice *dldContent)
     448{
     449        QHttpMultiPart mp(QHttpMultiPart::FormDataType);
     450        QHttpPart part;
     451        part.setRawHeader("Content-Disposition", "form-data; name=\"userfile\"");
     452        part.setBodyDevice(dldContent);
     453        mp.append(part);
     454
     455        multipart = &mp;
     456        hideDownload();
     457        exec();
     458        multipart = NULL;
     459
     460        delete reply; // we need to ensure it has stopped using our QHttpMultiPart
     461}
     462
    335463DivelogsDeWebServices::DivelogsDeWebServices(QWidget* parent, Qt::WindowFlags f): WebServices(parent, f)
    336464{
    337 
     465        QSettings s;
     466        ui.userID->setText(s.value("divelogde_user").toString());
     467        ui.password->setText(s.value("divelogde_pass").toString());
     468        hideUpload();
    338469}
    339470
    340471void DivelogsDeWebServices::startUpload()
    341472{
    342 
     473        ui.status->setText(tr("Uploading dive list..."));
     474        ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin'
     475        ui.upload->setEnabled(false);
     476        ui.userID->setEnabled(false);
     477        ui.password->setEnabled(false);
     478
     479        QNetworkRequest request;
     480        request.setUrl(QUrl("https://divelogs.de/DivelogsDirectImport.php"));
     481        request.setRawHeader("Accept", "text/xml, application/xml");
     482
     483        QHttpPart part;
     484        part.setRawHeader("Content-Disposition", "form-data; name=\"user\"");
     485        part.setBody(ui.userID->text().toUtf8());
     486        multipart->append(part);
     487
     488        part.setRawHeader("Content-Disposition", "form-data; name=\"pass\"");
     489        part.setBody(ui.password->text().toUtf8());
     490        multipart->append(part);
     491
     492        reply = manager()->post(request, multipart);
     493        connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished()));
     494        connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
     495                SLOT(uploadError(QNetworkReply::NetworkError)));
     496        connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this,
     497                SLOT(updateProgress(qint64,qint64)));
     498
     499        timeout.start(30000); // 30s
    343500}
    344501
    345502void DivelogsDeWebServices::startDownload()
    346503{
    347 
     504        ui.status->setText(tr("Downloading dive list..."));
     505        ui.progressBar->setRange(0,0); // this makes the progressbar do an 'infinite spin'
     506        ui.download->setEnabled(false);
     507        ui.userID->setEnabled(false);
     508        ui.password->setEnabled(false);
     509
     510        QNetworkRequest request;
     511        request.setUrl(QUrl("https://divelogs.de/xml_available_dives.php"));
     512        request.setRawHeader("Accept", "text/xml, application/xml");
     513        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
     514
     515#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
     516        QUrl body;
     517        body.addQueryItem("user", ui.userID->text());
     518        body.addQueryItem("pass", ui.password->text());
     519
     520        reply = manager()->post(request, body.encodedQuery());
     521#else
     522        QUrlQuery body;
     523        body.addQueryItem("user", ui.userID->text());
     524        body.addQueryItem("pass", ui.password->text());
     525
     526        reply = manager()->post(request, body.query(QUrl::FullyEncoded).toLatin1())
     527#endif
     528        connect(reply, SIGNAL(finished()), this, SLOT(listDownloadFinished()));
     529        connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
     530                this, SLOT(downloadError(QNetworkReply::NetworkError)));
     531
     532        timeout.start(30000); // 30s
     533}
     534
     535void DivelogsDeWebServices::listDownloadFinished()
     536{
     537        if (!reply)
     538                return;
     539        QByteArray xmlData = reply->readAll();
     540        reply->deleteLater();
     541        reply = NULL;
     542
     543        // parse the XML data we downloaded
     544        DiveListResult diveList = parseDiveLogsDeDiveList(xmlData);
     545        if (!diveList.errorCondition.isEmpty()) {
     546                // error condition
     547                resetState();
     548                ui.status->setText(diveList.errorCondition);
     549                return;
     550        }
     551
     552        ui.status->setText(tr("Downloading %1 dives...").arg(diveList.idCount));
     553
     554        QNetworkRequest request;
     555//      request.setUrl(QUrl("https://divelogs.de/DivelogsDirectExport.php"));
     556        request.setUrl(QUrl("http://divelogs.de/DivelogsDirectExport.php"));
     557        request.setRawHeader("Accept", "application/zip, */*");
     558        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
     559
     560#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
     561        QUrl body;
     562        body.addQueryItem("user", ui.userID->text());
     563        body.addQueryItem("pass", ui.password->text());
     564        body.addQueryItem("ids", diveList.idList);
     565
     566        reply = manager()->post(request, body.encodedQuery());
     567#else
     568        QUrlQuery body;
     569        body.addQueryItem("user", ui.userID->text());
     570        body.addQueryItem("pass", ui.password->text());
     571        body.addQueryItem("ids", diveList.idList);
     572
     573        reply = manager()->post(request, body.query(QUrl::FullyEncoded).toLatin1())
     574#endif
     575
     576        connect(reply, SIGNAL(readyRead()), this, SLOT(saveToZipFile()));
     577        connectSignalsForDownload(reply);
     578}
     579
     580void DivelogsDeWebServices::saveToZipFile()
     581{
     582        if (!zipFile.isOpen()) {
     583                zipFile.setFileTemplate(QDir::tempPath() + "/import-XXXXXX.dld");
     584                zipFile.open();
     585        }
     586
     587        zipFile.write(reply->readAll());
    348588}
    349589
    350590void DivelogsDeWebServices::downloadFinished()
    351591{
    352 
     592        if (!reply)
     593                return;
     594
     595        ui.download->setEnabled(true);
     596        ui.status->setText(tr("Download finished - %1").arg(reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
     597        reply->deleteLater();
     598        reply = NULL;
     599
     600        int errorcode;
     601        zipFile.seek(0);
     602#ifdef Q_OS_UNIX
     603        int duppedfd = dup(zipFile.handle());
     604        struct zip *zip = zip_fdopen(duppedfd, 0, &errorcode);
     605        if (!zip)
     606                ::close(duppedfd);
     607#else
     608        struct zip *zip = zip_open(zipFile.fileName(), 0, &errorcode);
     609#endif
     610        if (!zip) {
     611                char buf[512];
     612                zip_error_to_str(buf, sizeof(buf), errorcode, errno);
     613                QMessageBox::critical(this, tr("Corrupted download"),
     614                                      tr("The archive could not be opened:\n%1").arg(QString::fromLocal8Bit(buf)));
     615                zipFile.close();
     616                return;
     617        }
     618
     619        quint64 entries = zip_get_num_entries(zip, 0);
     620        for (quint64 i = 0; i < entries; ++i) {
     621                struct zip_file *zip_file = zip_fopen_index(zip, i, 0);
     622                if (!zip_file) {
     623                        QMessageBox::critical(this, tr("Corrupted download"),
     624                                              tr("The archive contains corrupt data:\n%1").arg(QString::fromLocal8Bit(zip_strerror(zip))));
     625                        goto close_zip;
     626                }
     627
     628                // ### FIXME: What do I do with this?
     629
     630                zip_fclose(zip_file);
     631        }
     632
     633close_zip:
     634        zip_close(zip);
     635        zipFile.close();
     636}
     637
     638void DivelogsDeWebServices::uploadFinished()
     639{
     640        if (!reply)
     641                return;
     642
     643        ui.progressBar->setRange(0,1);
     644        ui.upload->setEnabled(true);
     645        ui.status->setText(tr("Upload finished"));
     646
     647        // check what the server sent us: it might contain
     648        // an error condition, such as a failed login
     649        QByteArray xmlData = reply->readAll();
     650
     651        // ### FIXME: what's the format?
    353652}
    354653
     
    358657}
    359658
    360 void DivelogsDeWebServices::downloadError(QNetworkReply::NetworkError error)
    361 {
    362 
     659void DivelogsDeWebServices::downloadError(QNetworkReply::NetworkError)
     660{
     661        resetState();
     662        ui.status->setText(tr("Download error: %1").arg(reply->errorString()));
     663        reply->deleteLater();
     664        reply = NULL;
     665}
     666
     667void DivelogsDeWebServices::uploadError(QNetworkReply::NetworkError error)
     668{
     669        downloadError(error);
    363670}
    364671
     
    367674
    368675}
     676
  • qt-ui/subsurfacewebservices.h

    r919c7045 ra1972bc  
    44#include <QDialog>
    55#include <QNetworkReply>
     6#include <QTemporaryFile>
    67#include <QTimer>
    78#include <libxml/tree.h>
     
    1112class QAbstractButton;
    1213class QNetworkReply;
     14class QHttpMultiPart;
    1315
    1416class WebServices : public QDialog{
     
    1820        void hidePassword();
    1921        void hideUpload();
     22        void hideDownload();
    2023
    2124        static QNetworkAccessManager *manager();
     
    3336        void resetState();
    3437        void connectSignalsForDownload(QNetworkReply *reply);
     38        void connectSignalsForUpload();
    3539
    3640        Ui::WebServices ui;
     
    6266public:
    6367        static DivelogsDeWebServices * instance();
     68        void downloadDives();
     69        void uploadDives(QIODevice *dldContent);
    6470
    6571private slots:
    6672        void startDownload();
    6773        void buttonClicked(QAbstractButton* button);
     74        void saveToZipFile();
     75        void listDownloadFinished();
    6876        void downloadFinished();
     77        void uploadFinished();
    6978        void downloadError(QNetworkReply::NetworkError error);
     79        void uploadError(QNetworkReply::NetworkError error);
    7080        void startUpload();
    7181private:
     
    7484        void download_dialog_traverse_xml(xmlNodePtr node, unsigned int *download_status);
    7585        unsigned int download_dialog_parse_response(const QByteArray& length);
     86
     87        QHttpMultiPart *multipart;
     88        QTemporaryFile zipFile;
    7689};
    7790
Note: See TracChangeset for help on using the changeset viewer.