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 : MJPEG server manager
0008  *
0009  * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2021      by Quoc Hưng Tran <quochungtran1999 at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "mjpegservermngr.h"
0017 
0018 // Qt includes
0019 
0020 #include <QApplication>
0021 #include <QStringList>
0022 #include <QFile>
0023 #include <QDomDocument>
0024 #include <QDomElement>
0025 #include <QTextStream>
0026 #include <QStandardPaths>
0027 
0028 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
0029 #   include <QTextCodec>
0030 #endif
0031 
0032 // KDE includes
0033 
0034 #include <klocalizedstring.h>
0035 #include <ksharedconfig.h>
0036 #include <kconfiggroup.h>
0037 
0038 // Local includes
0039 
0040 #include "digikam_debug.h"
0041 #include "dnotificationwrapper.h"
0042 #include "mjpegframethread.h"
0043 
0044 using namespace Digikam;
0045 
0046 namespace DigikamGenericMjpegStreamPlugin
0047 {
0048 
0049 class Q_DECL_HIDDEN MjpegServerMngrCreator
0050 {
0051 public:
0052 
0053     MjpegServerMngr object;
0054 };
0055 
0056 Q_GLOBAL_STATIC(MjpegServerMngrCreator, creator)
0057 
0058 // ---------------------------------------------------------------------------------------------
0059 
0060 class Q_DECL_HIDDEN MjpegServerMngr::Private
0061 {
0062 public:
0063 
0064     explicit Private()
0065       : server(nullptr),
0066         thread(nullptr)
0067     {
0068     }
0069 
0070     /// Configuration XML file to store albums map to share in case of restoring between sessions.
0071     QString              mapsConf;
0072 
0073     /// MJPEG Server instance pointer.
0074     MjpegServer*         server;
0075 
0076     /// Frames generateur thread.
0077     MjpegFrameThread*    thread;
0078 
0079     /// The current albums collection to share.
0080     MjpegServerMap       collectionMap;
0081 
0082     /// The MJPEG stream settings.
0083     MjpegStreamSettings  settings;
0084 
0085     static const QString configGroupName;
0086     static const QString configStartServerOnStartupEntry;
0087 };
0088 
0089 const QString MjpegServerMngr::Private::configGroupName(QLatin1String("MJPEG Settings"));
0090 const QString MjpegServerMngr::Private::configStartServerOnStartupEntry(QLatin1String("Start MjpegServer At Startup"));
0091 
0092 MjpegServerMngr* MjpegServerMngr::instance()
0093 {
0094     return &creator->object;
0095 }
0096 
0097 MjpegServerMngr::MjpegServerMngr()
0098     : d(new Private)
0099 {
0100     d->mapsConf = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
0101                   QLatin1String("/mjpegserver.xml");
0102 }
0103 
0104 MjpegServerMngr::~MjpegServerMngr()
0105 {
0106     delete d;
0107 }
0108 
0109 QString MjpegServerMngr::configGroupName() const
0110 {
0111     return d->configGroupName;
0112 }
0113 
0114 QString MjpegServerMngr::configStartServerOnStartupEntry() const
0115 {
0116     return d->configStartServerOnStartupEntry;
0117 }
0118 
0119 void MjpegServerMngr::cleanUp()
0120 {
0121     if (d->thread)
0122     {
0123         d->thread->cancel();
0124         delete d->thread;
0125         d->thread = nullptr;
0126     }
0127 
0128     if (d->server)
0129     {
0130         d->server->stop();
0131         delete d->server;
0132         d->server = nullptr;
0133     }
0134 }
0135 
0136 bool MjpegServerMngr::loadAtStartup()
0137 {
0138     KSharedConfig::Ptr config     = KSharedConfig::openConfig();
0139     KConfigGroup mjpegConfigGroup = config->group(configGroupName());
0140     bool startServerOnStartup     = mjpegConfigGroup.readEntry(configStartServerOnStartupEntry(), false);
0141     bool result                   = true;
0142 
0143     if (startServerOnStartup)
0144     {
0145         // Restore the old sharing configuration and start the server.
0146 
0147         result &= load();
0148         result &= startMjpegServer();
0149 
0150         mjpegServerNotification(result);
0151 
0152         return result;
0153     }
0154 
0155     return false;
0156 }
0157 
0158 void MjpegServerMngr::saveAtShutdown()
0159 {
0160     KSharedConfig::Ptr config     = KSharedConfig::openConfig();
0161     KConfigGroup mjpegConfigGroup = config->group(configGroupName());
0162     bool startServerOnStartup     = mjpegConfigGroup.readEntry(configStartServerOnStartupEntry(), false);
0163 
0164     if (startServerOnStartup)
0165     {
0166         // Save the current sharing configuration for the next session.
0167 
0168         save();
0169     }
0170 
0171     cleanUp();
0172 }
0173 
0174 void MjpegServerMngr::mjpegServerNotification(bool started)
0175 {
0176     DNotificationWrapper(QLatin1String("mjpegserverloadstartup"),
0177                          started ? i18n("MJPEG Server have been started")
0178                                  : i18n("MJPEG Server cannot be started!"),
0179                          qApp->activeWindow(), qApp->applicationName());
0180 }
0181 
0182 void MjpegServerMngr::setItemsList(const QString& aname, const QList<QUrl>& urls)
0183 {
0184     d->collectionMap.clear();
0185     d->collectionMap.insert(aname, urls);
0186 }
0187 
0188 QList<QUrl> MjpegServerMngr::itemsList() const
0189 {
0190     QList<QUrl> ret;
0191 
0192     if (!d->collectionMap.isEmpty())
0193     {
0194         QList<QList<QUrl> > ulst = d->collectionMap.values();
0195 
0196         Q_FOREACH (const QList<QUrl>& urls, ulst)
0197         {
0198             ret << urls;
0199         }
0200     }
0201 
0202     return ret;
0203 }
0204 
0205 void MjpegServerMngr::setCollectionMap(const MjpegServerMap& map)
0206 {
0207     d->collectionMap = map;
0208 }
0209 
0210 MjpegServerMap MjpegServerMngr::collectionMap() const
0211 {
0212     return d->collectionMap;
0213 }
0214 
0215 void MjpegServerMngr::setSettings(const MjpegStreamSettings& set)
0216 {
0217     d->settings = set;
0218 }
0219 
0220 MjpegStreamSettings MjpegServerMngr::settings() const
0221 {
0222     return d->settings;
0223 }
0224 
0225 bool MjpegServerMngr::startMjpegServer()
0226 {
0227     if (!d->server)
0228     {
0229         d->server = new MjpegServer(QString(), d->settings.port);
0230         d->server->setRate(d->settings.rate);
0231         d->server->start();
0232     }
0233 
0234     if (d->collectionMap.isEmpty())
0235     {
0236         cleanUp();
0237 
0238         return false;
0239     }
0240 
0241     d->thread = new MjpegFrameThread(this);
0242     d->settings.setCollectionMap(d->collectionMap);
0243     d->thread->createFrameJob(d->settings);
0244 
0245     connect(d->thread, SIGNAL(signalFrameChanged(QByteArray)),
0246             d->server, SLOT(slotWriteFrame(QByteArray)));
0247 
0248     d->thread->start();
0249 
0250     return true;
0251 }
0252 
0253 bool MjpegServerMngr::isRunning() const
0254 {
0255     return (d->server ? true : false);
0256 }
0257 
0258 int MjpegServerMngr::albumsShared() const
0259 {
0260     if (d->collectionMap.isEmpty())
0261     {
0262         return 0;
0263     }
0264 
0265     return d->collectionMap.count();
0266 }
0267 
0268 int MjpegServerMngr::itemsShared() const
0269 {
0270     return itemsList().count();
0271 }
0272 
0273 bool MjpegServerMngr::save()
0274 {
0275     QDomDocument doc(QLatin1String("mjpegserverlist"));
0276     doc.setContent(QString::fromUtf8("<!DOCTYPE XMLQueueList><mjpegserverlist version=\"1.0\" client=\"digikam\" encoding=\"UTF-8\"/>"));
0277     QDomElement docElem = doc.documentElement();
0278     auto end            = d->collectionMap.cend();
0279 
0280     for (auto it = d->collectionMap.cbegin() ; it != end ; ++it)
0281     {
0282         QDomElement elm = doc.createElement(QLatin1String("album"));
0283         elm.setAttribute(QLatin1String("title"), it.key());
0284 
0285         // ----------------------
0286 
0287         QDomElement data;
0288 
0289         Q_FOREACH (const QUrl& url, it.value())
0290         {
0291             data = doc.createElement(QLatin1String("path"));
0292             data.setAttribute(QLatin1String("value"), url.toLocalFile());
0293             elm.appendChild(data);
0294         }
0295 
0296         docElem.appendChild(elm);
0297     }
0298 
0299     QFile file(d->mapsConf);
0300 
0301     if (!file.open(QIODevice::WriteOnly))
0302     {
0303         qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot open XML file to store MjpegServer list";
0304         qCDebug(DIGIKAM_MEDIASRV_LOG) << file.fileName();
0305 
0306         return false;
0307     }
0308 
0309     QTextStream stream(&file);
0310 
0311 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
0312     // In Qt5 only. Qt6 uses UTF-8 by default.
0313     stream.setCodec(QTextCodec::codecForName("UTF-8"));
0314 #endif
0315 
0316     stream.setAutoDetectUnicode(true);
0317     stream << doc.toString(4);
0318     file.close();
0319 
0320     return true;
0321 }
0322 
0323 bool MjpegServerMngr::load()
0324 {
0325     QFile file(d->mapsConf);
0326 
0327     if (file.exists())
0328     {
0329         if (!file.open(QIODevice::ReadOnly))
0330         {
0331             qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot open XML file to load MjpegServer list";
0332 
0333             return false;
0334         }
0335 
0336         QDomDocument doc(QLatin1String("mjpegserverlist"));
0337 
0338         if (!doc.setContent(&file))
0339         {
0340             qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot load MjpegServer list XML file";
0341             file.close();
0342 
0343             return false;
0344         }
0345 
0346         QDomElement    docElem = doc.documentElement();
0347         MjpegServerMap map;
0348         QList<QUrl>    urls;
0349         QString        album;
0350 
0351         for (QDomNode n = docElem.firstChild() ; !n.isNull() ; n = n.nextSibling())
0352         {
0353             QDomElement e = n.toElement();
0354 
0355             if (e.isNull())
0356             {
0357                 continue;
0358             }
0359 
0360             if (e.tagName() != QLatin1String("album"))
0361             {
0362                 continue;
0363             }
0364 
0365             album = e.attribute(QLatin1String("title"));
0366             urls.clear();
0367 
0368             for (QDomNode n2 = e.firstChild() ; !n2.isNull() ; n2 = n2.nextSibling())
0369             {
0370                 QDomElement e2 = n2.toElement();
0371 
0372                 if (e2.isNull())
0373                 {
0374                     continue;
0375                 }
0376 
0377                 QString name2  = e2.tagName();
0378                 QString val2   = e2.attribute(QLatin1String("value"));
0379 
0380                 if (name2 == QLatin1String("path"))
0381                 {
0382                     urls << QUrl::fromLocalFile(val2);
0383                 }
0384             }
0385 
0386             map.insert(album, urls);
0387         }
0388 
0389         setCollectionMap(map);
0390         file.close();
0391 
0392         return true;
0393     }
0394     else
0395     {
0396         return false;
0397     }
0398 }
0399 
0400 } // namespace DigikamGenericMjpegStreamPlugin
0401 
0402 #include "moc_mjpegservermngr.cpp"