httpwindow.cpp Example File

http/httpwindow.cpp
 /****************************************************************************
 **
 ** Copyright (C) 2016 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** BSD License Usage
 ** Alternatively, you may use this file under the terms of the BSD license
 ** as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of The Qt Company Ltd nor the names of its
 **     contributors may be used to endorse or promote products derived
 **     from this software without specific prior written permission.
 **
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 #include <QtWidgets>
 #include <QtNetwork>
 #include <QUrl>

 #include "httpwindow.h"
 #include "ui_authenticationdialog.h"

 #if QT_CONFIG(ssl)
 const char defaultUrl[] = "https://www.qt.io/";
 #else
 const char defaultUrl[] = "http://www.qt.io/";
 #endif
 const char defaultFileName[] = "index.html";

 ProgressDialog::ProgressDialog(const QUrl &url, QWidget *parent)
   : QProgressDialog(parent)
 {
     setWindowTitle(tr("Download Progress"));
     setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
     setLabelText(tr("Downloading %1.").arg(url.toDisplayString()));
     setMinimum(0);
     setValue(0);
     setMinimumDuration(0);
     setMinimumSize(QSize(400, 75));
 }

 void ProgressDialog::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
 {
     setMaximum(totalBytes);
     setValue(bytesRead);
 }

 HttpWindow::HttpWindow(QWidget *parent)
     : QDialog(parent)
     , statusLabel(new QLabel(tr("Please enter the URL of a file you want to download.\n\n"), this))
     , urlLineEdit(new QLineEdit(defaultUrl))
     , downloadButton(new QPushButton(tr("Download")))
     , launchCheckBox(new QCheckBox("Launch file"))
     , defaultFileLineEdit(new QLineEdit(defaultFileName))
     , downloadDirectoryLineEdit(new QLineEdit)
     , reply(nullptr)
     , file(nullptr)
     , httpRequestAborted(false)
 {
     setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
     setWindowTitle(tr("HTTP"));

     connect(&qnam, &QNetworkAccessManager::authenticationRequired,
             this, &HttpWindow::slotAuthenticationRequired);
 #ifndef QT_NO_SSL
     connect(&qnam, &QNetworkAccessManager::sslErrors,
             this, &HttpWindow::sslErrors);
 #endif

     QFormLayout *formLayout = new QFormLayout;
     urlLineEdit->setClearButtonEnabled(true);
     connect(urlLineEdit, &QLineEdit::textChanged,
             this, &HttpWindow::enableDownloadButton);
     formLayout->addRow(tr("&URL:"), urlLineEdit);
     QString downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
     if (downloadDirectory.isEmpty() || !QFileInfo(downloadDirectory).isDir())
         downloadDirectory = QDir::currentPath();
     downloadDirectoryLineEdit->setText(QDir::toNativeSeparators(downloadDirectory));
     formLayout->addRow(tr("&Download directory:"), downloadDirectoryLineEdit);
     formLayout->addRow(tr("Default &file:"), defaultFileLineEdit);
     launchCheckBox->setChecked(true);
     formLayout->addRow(launchCheckBox);

     QVBoxLayout *mainLayout = new QVBoxLayout(this);
     mainLayout->addLayout(formLayout);

     mainLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));

     statusLabel->setWordWrap(true);
     mainLayout->addWidget(statusLabel);

     downloadButton->setDefault(true);
     connect(downloadButton, &QAbstractButton::clicked, this, &HttpWindow::downloadFile);
     QPushButton *quitButton = new QPushButton(tr("Quit"));
     quitButton->setAutoDefault(false);
     connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
     QDialogButtonBox *buttonBox = new QDialogButtonBox;
     buttonBox->addButton(downloadButton, QDialogButtonBox::ActionRole);
     buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
     mainLayout->addWidget(buttonBox);

     urlLineEdit->setFocus();
 }

 void HttpWindow::startRequest(const QUrl &requestedUrl)
 {
     url = requestedUrl;
     httpRequestAborted = false;

     reply = qnam.get(QNetworkRequest(url));
     connect(reply, &QNetworkReply::finished, this, &HttpWindow::httpFinished);
     connect(reply, &QIODevice::readyRead, this, &HttpWindow::httpReadyRead);

     ProgressDialog *progressDialog = new ProgressDialog(url, this);
     progressDialog->setAttribute(Qt::WA_DeleteOnClose);
     connect(progressDialog, &QProgressDialog::canceled, this, &HttpWindow::cancelDownload);
     connect(reply, &QNetworkReply::downloadProgress, progressDialog, &ProgressDialog::networkReplyProgress);
     connect(reply, &QNetworkReply::finished, progressDialog, &ProgressDialog::hide);
     progressDialog->show();

     statusLabel->setText(tr("Downloading %1...").arg(url.toString()));
 }

 void HttpWindow::downloadFile()
 {
     const QString urlSpec = urlLineEdit->text().trimmed();
     if (urlSpec.isEmpty())
         return;

     const QUrl newUrl = QUrl::fromUserInput(urlSpec);
     if (!newUrl.isValid()) {
         QMessageBox::information(this, tr("Error"),
                                  tr("Invalid URL: %1: %2").arg(urlSpec, newUrl.errorString()));
         return;
     }

     QString fileName = newUrl.fileName();
     if (fileName.isEmpty())
         fileName = defaultFileLineEdit->text().trimmed();
     if (fileName.isEmpty())
         fileName = defaultFileName;
     QString downloadDirectory = QDir::cleanPath(downloadDirectoryLineEdit->text().trimmed());
     bool useDirectory = !downloadDirectory.isEmpty() && QFileInfo(downloadDirectory).isDir();
     if (useDirectory)
         fileName.prepend(downloadDirectory + '/');
     if (QFile::exists(fileName)) {
         if (QMessageBox::question(this, tr("Overwrite Existing File"),
                                   tr("There already exists a file called %1%2."
                                      " Overwrite?")
                                      .arg(fileName,
                                           useDirectory
                                            ? QString()
                                            : QStringLiteral(" in the current directory")),
                                      QMessageBox::Yes | QMessageBox::No,
                                      QMessageBox::No)
             == QMessageBox::No) {
             return;
         }
         QFile::remove(fileName);
     }

     file = openFileForWrite(fileName);
     if (!file)
         return;

     downloadButton->setEnabled(false);

     // schedule the request
     startRequest(newUrl);
 }

 QFile *HttpWindow::openFileForWrite(const QString &fileName)
 {
     QScopedPointer<QFile> file(new QFile(fileName));
     if (!file->open(QIODevice::WriteOnly)) {
         QMessageBox::information(this, tr("Error"),
                                  tr("Unable to save the file %1: %2.")
                                  .arg(QDir::toNativeSeparators(fileName),
                                       file->errorString()));
         return nullptr;
     }
     return file.take();
 }

 void HttpWindow::cancelDownload()
 {
     statusLabel->setText(tr("Download canceled."));
     httpRequestAborted = true;
     reply->abort();
     downloadButton->setEnabled(true);
 }

 void HttpWindow::httpFinished()
 {
     QFileInfo fi;
     if (file) {
         fi.setFile(file->fileName());
         file->close();
         delete file;
         file = nullptr;
     }

     if (httpRequestAborted) {
         reply->deleteLater();
         reply = nullptr;
         return;
     }

     if (reply->error()) {
         QFile::remove(fi.absoluteFilePath());
         statusLabel->setText(tr("Download failed:\n%1.").arg(reply->errorString()));
         downloadButton->setEnabled(true);
         reply->deleteLater();
         reply = nullptr;
         return;
     }

     const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);

     reply->deleteLater();
     reply = nullptr;

     if (!redirectionTarget.isNull()) {
         const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl());
         if (QMessageBox::question(this, tr("Redirect"),
                                   tr("Redirect to %1 ?").arg(redirectedUrl.toString()),
                                   QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
             QFile::remove(fi.absoluteFilePath());
             downloadButton->setEnabled(true);
             statusLabel->setText(tr("Download failed:\nRedirect rejected."));
             return;
         }
         file = openFileForWrite(fi.absoluteFilePath());
         if (!file) {
             downloadButton->setEnabled(true);
             return;
         }
         startRequest(redirectedUrl);
         return;
     }

     statusLabel->setText(tr("Downloaded %1 bytes to %2\nin\n%3")
                          .arg(fi.size()).arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath())));
     if (launchCheckBox->isChecked())
         QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath()));
     downloadButton->setEnabled(true);
 }

 void HttpWindow::httpReadyRead()
 {
     // this slot gets called every time the QNetworkReply has new data.
     // We read all of its new data and write it into the file.
     // That way we use less RAM than when reading it at the finished()
     // signal of the QNetworkReply
     if (file)
         file->write(reply->readAll());
 }

 void HttpWindow::enableDownloadButton()
 {
     downloadButton->setEnabled(!urlLineEdit->text().isEmpty());
 }

 void HttpWindow::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator)
 {
     QDialog authenticationDialog;
     Ui::Dialog ui;
     ui.setupUi(&authenticationDialog);
     authenticationDialog.adjustSize();
     ui.siteDescription->setText(tr("%1 at %2").arg(authenticator->realm(), url.host()));

     // Did the URL have information? Fill the UI
     // This is only relevant if the URL-supplied credentials were wrong
     ui.userEdit->setText(url.userName());
     ui.passwordEdit->setText(url.password());

     if (authenticationDialog.exec() == QDialog::Accepted) {
         authenticator->setUser(ui.userEdit->text());
         authenticator->setPassword(ui.passwordEdit->text());
     }
 }

 #ifndef QT_NO_SSL
 void HttpWindow::sslErrors(QNetworkReply *, const QList<QSslError> &errors)
 {
     QString errorString;
     foreach (const QSslError &error, errors) {
         if (!errorString.isEmpty())
             errorString += '\n';
         errorString += error.errorString();
     }

     if (QMessageBox::warning(this, tr("SSL Errors"),
                              tr("One or more SSL errors has occurred:\n%1").arg(errorString),
                              QMessageBox::Ignore | QMessageBox::Abort) == QMessageBox::Ignore) {
         reply->ignoreSslErrors();
     }
 }
 #endif