File indexing completed on 2025-01-05 03:53:06

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2021-07-24
0007  * Description : a MJPEG Stream server to export items on the network.
0008  *
0009  * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "mjpegserver_p.h"
0016 
0017 // Must be placed in first to not break Windows Compilation with WinSock API
0018 
0019 #ifdef Q_OS_WIN
0020 #   ifdef WIN32_LEAN_AND_MEAN        // krazy:exclude=cpp
0021 #       undef WIN32_LEAN_AND_MEAN
0022 #   endif
0023 #endif
0024 
0025 // C ANSI includes
0026 
0027 #ifndef Q_OS_WIN
0028 #   include <sys/socket.h>
0029 #else
0030 #   include <windows.h>
0031 #   define MSG_NOSIGNAL 0
0032 #endif
0033 
0034 // Qt includes
0035 
0036 #include <QString>
0037 #include <QBuffer>
0038 #include <QtConcurrent>              // krazy:exclude=includes
0039 
0040 // Local includes
0041 
0042 #include "actionthreadbase.h"
0043 
0044 namespace DigikamGenericMjpegStreamPlugin
0045 {
0046 
0047 MjpegServer::Private::Private(QObject* const parent)
0048     : QObject(parent),
0049       server (nullptr),
0050       rate   (15),
0051       delay  (40000)
0052 {
0053 }
0054 
0055 MjpegServer::Private::~Private()
0056 {
0057 }
0058 
0059 void MjpegServer::Private::setMaxClients(int max)
0060 {
0061     if (isOpened())
0062     {
0063         server->setMaxPendingConnections(max);
0064         qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server max clients:" << maxClients();
0065     }
0066 }
0067 
0068 int MjpegServer::Private::maxClients() const
0069 {
0070     if (isOpened())
0071     {
0072         return server->maxPendingConnections();
0073     }
0074 
0075     return (-1);
0076 }
0077 
0078 int MjpegServer::Private::writeInSocket(int sock, const QByteArray& data) const
0079 {
0080     if (!data.isEmpty())
0081     {
0082         try
0083         {
0084             return (::send(sock, data.constData(), data.size(), MSG_NOSIGNAL));
0085         }
0086         catch (int e)
0087         {
0088             qCDebug(DIGIKAM_GENERAL_LOG) << "Socket::send() exception occurred:" << e;
0089         }
0090     }
0091 
0092     return (-1);
0093 }
0094 
0095 QString MjpegServer::Private::clientDescription(QTcpSocket* const client) const
0096 {
0097     return (QString::fromLatin1("%1:%2").arg(client->peerAddress().toString())
0098                                         .arg(client->peerPort()));
0099 }
0100 
0101 bool MjpegServer::Private::isOpened() const
0102 {
0103     if (!server)
0104     {
0105         return false;
0106     }
0107 
0108     return (server->isListening());
0109 }
0110 
0111 bool MjpegServer::Private::open(const QString& address, int port)
0112 {
0113     server = new QTcpServer(parent());
0114 
0115     connect(server, SIGNAL(newConnection()),
0116             SLOT(slotNewConnection()));
0117 
0118     if (!server->listen(address.isEmpty() ? QHostAddress::Any
0119                                           : QHostAddress(address),
0120                         port))
0121     {
0122         qCWarning(DIGIKAM_GENERAL_LOG) << "Error : couldn't listen with server"
0123                                        << server->serverAddress()
0124                                        << "to port" << server->serverPort() << "!";
0125         close();
0126         return false;
0127     }
0128 
0129     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server address    :" << server->serverAddress();
0130     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server port       :" << server->serverPort();
0131 
0132     return true;
0133 }
0134 
0135 void MjpegServer::Private::close()
0136 {
0137     if (isOpened())
0138     {
0139         server->close();
0140     }
0141 
0142     server->deleteLater();
0143 }
0144 
0145 void MjpegServer::Private::start()
0146 {
0147     srvTask = QtConcurrent::run(
0148 
0149 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0150 
0151                                 &MjpegServer::Private::writerThread, this
0152 
0153 #else
0154 
0155                                 this, &MjpegServer::Private::writerThread
0156 
0157 #endif
0158 
0159     );
0160 
0161     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server started...";
0162 }
0163 
0164 void MjpegServer::Private::stop()
0165 {
0166     close();
0167     srvTask.waitForFinished();
0168     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server stopped...";
0169 }
0170 
0171 void MjpegServer::Private::slotNewConnection()
0172 {
0173     while (server->hasPendingConnections())
0174     {
0175         QTcpSocket* const client = server->nextPendingConnection();
0176 
0177         if (client)
0178         {
0179             if (!blackList.contains(client->peerAddress().toString()))
0180             {
0181                 connect(client, SIGNAL(disconnected()),
0182                         this, SLOT(slotClientDisconnected()));
0183 
0184                 mutexClients.lock();
0185                 {
0186                     client->write(QByteArray("HTTP/1.0 200 OK\r\n"));
0187 
0188                     client->write(QByteArray("Server: digiKamMjpeg/1.0\r\n"
0189                                              "Accept-Range: bytes\r\n"
0190                                              "Connection: close\r\n"
0191                                              "Max-Age: 0\r\n"
0192                                              "Expires: 0\r\n"
0193                                              "Cache-Control: no-cache, private\r\n"
0194                                              "Pragma: no-cache\r\n"
0195                                              "Content-Type: multipart/x-mixed-replace; boundary=--mjpegstream\r\n"
0196                                              "\r\n"));
0197 
0198                     clients.push_back(client);
0199 
0200                     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server new client    :" << clientDescription(client);
0201                     qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server total clients :" << clients.count();
0202                 }
0203                 mutexClients.unlock();
0204             }
0205             else
0206             {
0207                 client->close();
0208             }
0209         }
0210     }
0211 }
0212 
0213 void MjpegServer::Private::slotClientDisconnected()
0214 {
0215     QTcpSocket* const client = dynamic_cast<QTcpSocket*>(sender());
0216 
0217     if (!client)
0218     {
0219         return;
0220     }
0221 
0222     mutexClients.lock();
0223     {
0224         int index = clients.indexOf(client);
0225 
0226         if (index != -1)
0227         {
0228             clients.removeAt(index);
0229 
0230             qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server client disconnected :" << clientDescription(client);
0231             qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server total clients       :" << clients.count();
0232 
0233             client->deleteLater();
0234         }
0235     }
0236     mutexClients.unlock();
0237 }
0238 
0239 // -----------------------------------------------------
0240 // Multi-threaded methods.
0241 
0242 void MjpegServer::Private::writerThread()
0243 {
0244     while (isOpened())
0245     {
0246         QList <QFuture<void> > sockTasks;
0247 
0248         mutexFrame.lock();
0249         {
0250             mutexClients.lock();
0251             {
0252                 Q_FOREACH (QTcpSocket* const client, clients)
0253                 {
0254                     sockTasks.append(QtConcurrent::run(
0255 
0256 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0257 
0258                                      &MjpegServer::Private::clientWriteMultithreaded, this,
0259 
0260 #else
0261 
0262                                      this, &MjpegServer::Private::clientWriteMultithreaded,
0263 
0264 #endif
0265 
0266                                      client->socketDescriptor(),
0267                                      lastFrame)
0268                                     );
0269                 }
0270             }
0271             mutexClients.unlock();
0272 
0273             Q_FOREACH (QFuture<void> t, sockTasks)
0274             {
0275                 t.waitForFinished();
0276             }
0277         }
0278         mutexFrame.unlock();
0279 
0280         QThread::usleep(delay);
0281     }
0282 }
0283 
0284 void MjpegServer::Private::clientWriteMultithreaded(int client, const QByteArray& data)
0285 {
0286     QString head;
0287     head.append(QLatin1String("--mjpegstream\r\n"
0288                               "Content-type: image/jpeg\r\n"
0289                               "Content-length: "));
0290     head.append(QString::number(data.size()));
0291     head.append(QLatin1String("\r\n\r\n"));
0292 
0293     // Write header
0294 
0295     (void)writeInSocket(client, head.toLatin1());
0296 
0297     // Write image data
0298 
0299     (void)writeInSocket(client, data);
0300     (void)writeInSocket(client, QByteArray("\r\n\r\n"));
0301 }
0302 
0303 } // namespace DigikamGenericMjpegStreamPlugin
0304 
0305 #include "moc_mjpegserver_p.cpp"