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"