summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorMårten Nordheim <marten.nordheim@qt.io>2023-02-27 15:16:34 +0100
committerMårten Nordheim <marten.nordheim@qt.io>2023-03-02 12:08:09 +0100
commiteb61d49ab3a4f4d9dae3ccfdaf416190fb168c27 (patch)
treefbb6b1a95eacbc3ce11d359ce99420b7e56e7acb /examples
parent8dbceaa398d92c9c7492ffeb483f2bd6fab30c17 (diff)
Delete the Network Download (Manager)? examples
Their use of QtNetwork is already covered by the HTTP example. While showcasing that QNAM easily deals with multiple simultaneous requests, waiting until finished() is emitted to write anything is not exactly idiomatic. And managing your own queue to only have one request running at a time is a weird example for an asynchronous framework. In this regard, having an example for a complete download manager (with a GUI) would be interesting, but may ultimately be very time-consuming to make for limited gain. Task-number: QTBUG-110643 Pick-to: 6.5 Change-Id: I6b2c1546b85fa89ab7ce1ff5565b0293b5710b74 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: Ievgenii Meshcheriakov <ievgenii.meshcheriakov@qt.io> Reviewed-by: Konrad Kujawa <konrad.kujawa@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/network/CMakeLists.txt2
-rw-r--r--examples/network/doc/src/network-download.qdoc17
-rw-r--r--examples/network/doc/src/network-downloadmanager.qdoc17
-rw-r--r--examples/network/download/CMakeLists.txt30
-rw-r--r--examples/network/download/download.pro8
-rw-r--r--examples/network/download/main.cpp166
-rw-r--r--examples/network/downloadmanager/CMakeLists.txt32
-rw-r--r--examples/network/downloadmanager/downloadmanager.cpp165
-rw-r--r--examples/network/downloadmanager/downloadmanager.h46
-rw-r--r--examples/network/downloadmanager/downloadmanager.pro17
-rw-r--r--examples/network/downloadmanager/main.cpp35
-rw-r--r--examples/network/downloadmanager/textprogressbar.cpp60
-rw-r--r--examples/network/downloadmanager/textprogressbar.h24
-rw-r--r--examples/network/network.pro3
14 files changed, 0 insertions, 622 deletions
diff --git a/examples/network/CMakeLists.txt b/examples/network/CMakeLists.txt
index 1b1cfa55f7..8c6587693c 100644
--- a/examples/network/CMakeLists.txt
+++ b/examples/network/CMakeLists.txt
@@ -4,8 +4,6 @@
if(NOT TARGET Qt6::Network)
return()
endif()
-qt_internal_add_example(download)
-qt_internal_add_example(downloadmanager)
if(NOT INTEGRITY)
qt_internal_add_example(dnslookup)
endif()
diff --git a/examples/network/doc/src/network-download.qdoc b/examples/network/doc/src/network-download.qdoc
deleted file mode 100644
index 807cebada2..0000000000
--- a/examples/network/doc/src/network-download.qdoc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
-
-/*!
- \example download
- \title Network Download Example
- \brief Demonstrates how to use networking APIs for multiple downloads.
- \ingroup examples-network
-
- The Network Download example shows how to perform multiple downloads in
- parallel using the QNetworkAccessManager class.
-
- This example is designed to be run from the command-line.
-
- The \l{Network Download Manager Example} implements a more complex system
- that places files in a queue for sequential downloading.
-*/
diff --git a/examples/network/doc/src/network-downloadmanager.qdoc b/examples/network/doc/src/network-downloadmanager.qdoc
deleted file mode 100644
index 6f5606b016..0000000000
--- a/examples/network/doc/src/network-downloadmanager.qdoc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
-
-/*!
- \example downloadmanager
- \title Network Download Manager Example
- \brief Demonstrates how to use the networking APIs for multiple downloads.
- \ingroup examples-network
-
- The Network Download example shows how to implement a queue for multiple
- downloads using the QNetworkAccessManager class.
-
- This example is designed to be run from the command-line.
-
- See the \l{Network Download Example} for a simpler version of this example
- that obtains multiple files in parallel.
-*/
diff --git a/examples/network/download/CMakeLists.txt b/examples/network/download/CMakeLists.txt
deleted file mode 100644
index 4fcf6d669d..0000000000
--- a/examples/network/download/CMakeLists.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-cmake_minimum_required(VERSION 3.16)
-project(download LANGUAGES CXX)
-
-if(NOT DEFINED INSTALL_EXAMPLESDIR)
- set(INSTALL_EXAMPLESDIR "examples")
-endif()
-
-set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/download")
-
-find_package(Qt6 REQUIRED COMPONENTS Core Network)
-
-qt_standard_project_setup()
-
-qt_add_executable(download
- main.cpp
-)
-
-target_link_libraries(download PRIVATE
- Qt6::Core
- Qt6::Network
-)
-
-install(TARGETS download
- RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
- BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
- LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
-)
diff --git a/examples/network/download/download.pro b/examples/network/download/download.pro
deleted file mode 100644
index 63d80a0e7c..0000000000
--- a/examples/network/download/download.pro
+++ /dev/null
@@ -1,8 +0,0 @@
-QT = core network
-CONFIG += cmdline
-
-SOURCES += main.cpp
-
-# install
-target.path = $$[QT_INSTALL_EXAMPLES]/network/download
-INSTALLS += target
diff --git a/examples/network/download/main.cpp b/examples/network/download/main.cpp
deleted file mode 100644
index 25543a439e..0000000000
--- a/examples/network/download/main.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#include <QtCore>
-#include <QtNetwork>
-
-#include <cstdio>
-
-QT_BEGIN_NAMESPACE
-class QSslError;
-QT_END_NAMESPACE
-
-using namespace std;
-
-class DownloadManager: public QObject
-{
- Q_OBJECT
- QNetworkAccessManager manager;
- QList<QNetworkReply *> currentDownloads;
-
-public:
- DownloadManager();
- void doDownload(const QUrl &url);
- static QString saveFileName(const QUrl &url);
- bool saveToDisk(const QString &filename, QIODevice *data);
- static bool isHttpRedirect(QNetworkReply *reply);
-
-public slots:
- void execute();
- void downloadFinished(QNetworkReply *reply);
- void sslErrors(const QList<QSslError> &errors);
-};
-
-DownloadManager::DownloadManager()
-{
- connect(&manager, &QNetworkAccessManager::finished,
- this, &DownloadManager::downloadFinished);
-}
-
-void DownloadManager::doDownload(const QUrl &url)
-{
- QNetworkRequest request(url);
- QNetworkReply *reply = manager.get(request);
-
-#if QT_CONFIG(ssl)
- connect(reply, &QNetworkReply::sslErrors,
- this, &DownloadManager::sslErrors);
-#endif
-
- currentDownloads.append(reply);
-}
-
-QString DownloadManager::saveFileName(const QUrl &url)
-{
- QString path = url.path();
- QString basename = QFileInfo(path).fileName();
-
- if (basename.isEmpty())
- basename = "download";
-
- if (QFile::exists(basename)) {
- // already exists, don't overwrite
- int i = 0;
- basename += '.';
- while (QFile::exists(basename + QString::number(i)))
- ++i;
-
- basename += QString::number(i);
- }
-
- return basename;
-}
-
-bool DownloadManager::saveToDisk(const QString &filename, QIODevice *data)
-{
- QFile file(filename);
- if (!file.open(QIODevice::WriteOnly)) {
- fprintf(stderr, "Could not open %s for writing: %s\n",
- qPrintable(filename),
- qPrintable(file.errorString()));
- return false;
- }
-
- file.write(data->readAll());
- file.close();
-
- return true;
-}
-
-bool DownloadManager::isHttpRedirect(QNetworkReply *reply)
-{
- int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- return statusCode == 301 || statusCode == 302 || statusCode == 303
- || statusCode == 305 || statusCode == 307 || statusCode == 308;
-}
-
-void DownloadManager::execute()
-{
- QStringList args = QCoreApplication::instance()->arguments();
- args.takeFirst(); // skip the first argument, which is the program's name
- if (args.isEmpty()) {
- printf("Qt Download example - downloads all URLs in parallel\n"
- "Usage: download url1 [url2... urlN]\n"
- "\n"
- "Downloads the URLs passed in the command-line to the local directory\n"
- "If the target file already exists, a .0, .1, .2, etc. is appended to\n"
- "differentiate.\n");
- QCoreApplication::instance()->quit();
- return;
- }
-
- for (const QString &arg : std::as_const(args)) {
- QUrl url = QUrl::fromEncoded(arg.toLocal8Bit());
- doDownload(url);
- }
-}
-
-void DownloadManager::sslErrors(const QList<QSslError> &sslErrors)
-{
-#if QT_CONFIG(ssl)
- for (const QSslError &error : sslErrors)
- fprintf(stderr, "SSL error: %s\n", qPrintable(error.errorString()));
-#else
- Q_UNUSED(sslErrors);
-#endif
-}
-
-void DownloadManager::downloadFinished(QNetworkReply *reply)
-{
- QUrl url = reply->url();
- if (reply->error()) {
- fprintf(stderr, "Download of %s failed: %s\n",
- url.toEncoded().constData(),
- qPrintable(reply->errorString()));
- } else {
- if (isHttpRedirect(reply)) {
- fputs("Request was redirected.\n", stderr);
- } else {
- QString filename = saveFileName(url);
- if (saveToDisk(filename, reply)) {
- printf("Download of %s succeeded (saved to %s)\n",
- url.toEncoded().constData(), qPrintable(filename));
- }
- }
- }
-
- currentDownloads.removeAll(reply);
- reply->deleteLater();
-
- if (currentDownloads.isEmpty()) {
- // all downloads finished
- QCoreApplication::instance()->quit();
- }
-}
-
-int main(int argc, char **argv)
-{
- QCoreApplication app(argc, argv);
-
- DownloadManager manager;
- QTimer::singleShot(0, &manager, &DownloadManager::execute);
-
- app.exec();
-}
-
-#include "main.moc"
diff --git a/examples/network/downloadmanager/CMakeLists.txt b/examples/network/downloadmanager/CMakeLists.txt
deleted file mode 100644
index 7b108e31ea..0000000000
--- a/examples/network/downloadmanager/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-cmake_minimum_required(VERSION 3.16)
-project(downloadmanager LANGUAGES CXX)
-
-if(NOT DEFINED INSTALL_EXAMPLESDIR)
- set(INSTALL_EXAMPLESDIR "examples")
-endif()
-
-set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/network/downloadmanager")
-
-find_package(Qt6 REQUIRED COMPONENTS Core Network)
-
-qt_standard_project_setup()
-
-qt_add_executable(downloadmanager
- downloadmanager.cpp downloadmanager.h
- main.cpp
- textprogressbar.cpp textprogressbar.h
-)
-
-target_link_libraries(downloadmanager PRIVATE
- Qt6::Core
- Qt6::Network
-)
-
-install(TARGETS downloadmanager
- RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
- BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
- LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
-)
diff --git a/examples/network/downloadmanager/downloadmanager.cpp b/examples/network/downloadmanager/downloadmanager.cpp
deleted file mode 100644
index ffb7362113..0000000000
--- a/examples/network/downloadmanager/downloadmanager.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#include "downloadmanager.h"
-
-#include <QTextStream>
-
-#include <cstdio>
-
-using namespace std;
-
-DownloadManager::DownloadManager(QObject *parent)
- : QObject(parent)
-{
-}
-
-void DownloadManager::append(const QStringList &urls)
-{
- for (const QString &urlAsString : urls)
- append(QUrl::fromEncoded(urlAsString.toLocal8Bit()));
-
- if (downloadQueue.isEmpty())
- QTimer::singleShot(0, this, &DownloadManager::finished);
-}
-
-void DownloadManager::append(const QUrl &url)
-{
- if (downloadQueue.isEmpty())
- QTimer::singleShot(0, this, &DownloadManager::startNextDownload);
-
- downloadQueue.enqueue(url);
- ++totalCount;
-}
-
-QString DownloadManager::saveFileName(const QUrl &url)
-{
- QString path = url.path();
- QString basename = QFileInfo(path).fileName();
-
- if (basename.isEmpty())
- basename = "download";
-
- if (QFile::exists(basename)) {
- // already exists, don't overwrite
- int i = 0;
- basename += '.';
- while (QFile::exists(basename + QString::number(i)))
- ++i;
-
- basename += QString::number(i);
- }
-
- return basename;
-}
-
-void DownloadManager::startNextDownload()
-{
- if (downloadQueue.isEmpty()) {
- printf("%d/%d files downloaded successfully\n", downloadedCount, totalCount);
- emit finished();
- return;
- }
-
- QUrl url = downloadQueue.dequeue();
-
- QString filename = saveFileName(url);
- output.setFileName(filename);
- if (!output.open(QIODevice::WriteOnly)) {
- fprintf(stderr, "Problem opening save file '%s' for download '%s': %s\n",
- qPrintable(filename), url.toEncoded().constData(),
- qPrintable(output.errorString()));
-
- startNextDownload();
- return; // skip this download
- }
-
- QNetworkRequest request(url);
- currentDownload = manager.get(request);
- connect(currentDownload, &QNetworkReply::downloadProgress,
- this, &DownloadManager::downloadProgress);
- connect(currentDownload, &QNetworkReply::finished,
- this, &DownloadManager::downloadFinished);
- connect(currentDownload, &QNetworkReply::readyRead,
- this, &DownloadManager::downloadReadyRead);
-
- // prepare the output
- printf("Downloading %s...\n", url.toEncoded().constData());
- downloadTimer.start();
-}
-
-void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
- progressBar.setStatus(bytesReceived, bytesTotal);
-
- // calculate the download speed
- double speed = bytesReceived * 1000.0 / downloadTimer.elapsed();
- QString unit;
- if (speed < 1024) {
- unit = "bytes/sec";
- } else if (speed < 1024*1024) {
- speed /= 1024;
- unit = "kB/s";
- } else {
- speed /= 1024*1024;
- unit = "MB/s";
- }
-
- progressBar.setMessage(QString::fromLatin1("%1 %2")
- .arg(speed, 3, 'f', 1).arg(unit));
- progressBar.update();
-}
-
-void DownloadManager::downloadFinished()
-{
- progressBar.clear();
- output.close();
-
- if (currentDownload->error()) {
- // download failed
- fprintf(stderr, "Failed: %s\n", qPrintable(currentDownload->errorString()));
- output.remove();
- } else {
- // let's check if it was actually a redirect
- if (isHttpRedirect()) {
- reportRedirect();
- output.remove();
- } else {
- printf("Succeeded.\n");
- ++downloadedCount;
- }
- }
-
- currentDownload->deleteLater();
- startNextDownload();
-}
-
-void DownloadManager::downloadReadyRead()
-{
- output.write(currentDownload->readAll());
-}
-
-bool DownloadManager::isHttpRedirect() const
-{
- int statusCode = currentDownload->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- return statusCode == 301 || statusCode == 302 || statusCode == 303
- || statusCode == 305 || statusCode == 307 || statusCode == 308;
-}
-
-void DownloadManager::reportRedirect()
-{
- int statusCode = currentDownload->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- QUrl requestUrl = currentDownload->request().url();
- QTextStream(stderr) << "Request: " << requestUrl.toDisplayString()
- << " was redirected with code: " << statusCode
- << '\n';
-
- QVariant target = currentDownload->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (!target.isValid())
- return;
- QUrl redirectUrl = target.toUrl();
- if (redirectUrl.isRelative())
- redirectUrl = requestUrl.resolved(redirectUrl);
- QTextStream(stderr) << "Redirected to: " << redirectUrl.toDisplayString()
- << '\n';
-}
diff --git a/examples/network/downloadmanager/downloadmanager.h b/examples/network/downloadmanager/downloadmanager.h
deleted file mode 100644
index 4293bce26b..0000000000
--- a/examples/network/downloadmanager/downloadmanager.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#ifndef DOWNLOADMANAGER_H
-#define DOWNLOADMANAGER_H
-
-#include <QtNetwork>
-#include <QtCore>
-
-#include "textprogressbar.h"
-
-class DownloadManager: public QObject
-{
- Q_OBJECT
-public:
- explicit DownloadManager(QObject *parent = nullptr);
-
- void append(const QUrl &url);
- void append(const QStringList &urls);
- static QString saveFileName(const QUrl &url);
-
-signals:
- void finished();
-
-private slots:
- void startNextDownload();
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
- void downloadFinished();
- void downloadReadyRead();
-
-private:
- bool isHttpRedirect() const;
- void reportRedirect();
-
- QNetworkAccessManager manager;
- QQueue<QUrl> downloadQueue;
- QNetworkReply *currentDownload = nullptr;
- QFile output;
- QElapsedTimer downloadTimer;
- TextProgressBar progressBar;
-
- int downloadedCount = 0;
- int totalCount = 0;
-};
-
-#endif
diff --git a/examples/network/downloadmanager/downloadmanager.pro b/examples/network/downloadmanager/downloadmanager.pro
deleted file mode 100644
index cd1a977e5d..0000000000
--- a/examples/network/downloadmanager/downloadmanager.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-QT = core network
-CONFIG += cmdline
-
-HEADERS += downloadmanager.h textprogressbar.h
-SOURCES += downloadmanager.cpp main.cpp textprogressbar.cpp
-
-# install
-target.path = $$[QT_INSTALL_EXAMPLES]/network/downloadmanager
-INSTALLS += target
-
-OTHER_FILES += \
- debian/changelog \
- debian/compat \
- debian/control \
- debian/copyright \
- debian/README \
- debian/rules
diff --git a/examples/network/downloadmanager/main.cpp b/examples/network/downloadmanager/main.cpp
deleted file mode 100644
index 6b99e38c63..0000000000
--- a/examples/network/downloadmanager/main.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#include <QCoreApplication>
-#include <QStringList>
-
-#include "downloadmanager.h"
-
-#include <cstdio>
-
-int main(int argc, char **argv)
-{
- using namespace std;
-
- QCoreApplication app(argc, argv);
- QStringList arguments = app.arguments();
- arguments.takeFirst(); // remove the first argument, which is the program's name
-
- if (arguments.isEmpty()) {
- printf("Qt Download example\n"
- "Usage: downloadmanager url1 [url2... urlN]\n"
- "\n"
- "Downloads the URLs passed in the command-line to the local directory\n"
- "If the target file already exists, a .0, .1, .2, etc. is appended to\n"
- "differentiate.\n");
- return 0;
- }
-
- DownloadManager manager;
- manager.append(arguments);
-
- QObject::connect(&manager, &DownloadManager::finished,
- &app, &QCoreApplication::quit);
- app.exec();
-}
diff --git a/examples/network/downloadmanager/textprogressbar.cpp b/examples/network/downloadmanager/textprogressbar.cpp
deleted file mode 100644
index 824b403cfc..0000000000
--- a/examples/network/downloadmanager/textprogressbar.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#include "textprogressbar.h"
-
-#include <QByteArray>
-
-#include <cstdio>
-
-using namespace std;
-
-void TextProgressBar::clear()
-{
- printf("\n");
- fflush(stdout);
-
- value = 0;
- maximum = -1;
- iteration = 0;
-}
-
-void TextProgressBar::update()
-{
- ++iteration;
-
- if (maximum > 0) {
- // we know the maximum
- // draw a progress bar
- int percent = value * 100 / maximum;
- int hashes = percent / 2;
-
- QByteArray progressbar(hashes, '#');
- if (percent % 2)
- progressbar += '>';
-
- printf("\r[%-50s] %3d%% %s ",
- progressbar.constData(),
- percent,
- qPrintable(message));
- } else {
- // we don't know the maximum, so we can't draw a progress bar
- int center = (iteration % 48) + 1; // 50 spaces, minus 2
- QByteArray before(qMax(center - 2, 0), ' ');
- QByteArray after(qMin(center + 2, 50), ' ');
-
- printf("\r[%s###%s] %s ",
- before.constData(), after.constData(), qPrintable(message));
- }
-}
-
-void TextProgressBar::setMessage(const QString &m)
-{
- message = m;
-}
-
-void TextProgressBar::setStatus(qint64 val, qint64 max)
-{
- value = val;
- maximum = max;
-}
diff --git a/examples/network/downloadmanager/textprogressbar.h b/examples/network/downloadmanager/textprogressbar.h
deleted file mode 100644
index 60ef0dfddc..0000000000
--- a/examples/network/downloadmanager/textprogressbar.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-#ifndef TEXTPROGRESSBAR_H
-#define TEXTPROGRESSBAR_H
-
-#include <QString>
-
-class TextProgressBar
-{
-public:
- void clear();
- void update();
- void setMessage(const QString &message);
- void setStatus(qint64 value, qint64 maximum);
-
-private:
- QString message;
- qint64 value = 0;
- qint64 maximum = -1;
- int iteration = 0;
-};
-
-#endif
diff --git a/examples/network/network.pro b/examples/network/network.pro
index f4db039c7d..fcd38d3116 100644
--- a/examples/network/network.pro
+++ b/examples/network/network.pro
@@ -2,9 +2,6 @@ requires(qtHaveModule(network))
TEMPLATE = subdirs
QT_FOR_CONFIG += network-private
-SUBDIRS = \
- download \
- downloadmanager
!integrity: SUBDIRS += dnslookup
qtHaveModule(widgets) {
SUBDIRS += \