diff --git a/src/appconfig.cpp b/src/appconfig.cpp index fe06e57..5532da7 100644 --- a/src/appconfig.cpp +++ b/src/appconfig.cpp @@ -180,6 +180,10 @@ QVector parsePathEntry(QJsonObject &jsonMainDoc, bool baseRecursive, if(!imageListString.empty()) { entry.imageList = imageListString; } + std::string rssFeedURLString = ParseJSONString(schedulerJson, "redditrss"); + if(!rssFeedURLString.empty()) { + entry.rssFeedURL = rssFeedURLString; + } SetJSONBool(entry.exclusive, schedulerJson, "exclusive"); @@ -229,7 +233,7 @@ AppConfig loadAppConfiguration(const AppConfig &commandLineConfig) { QJsonDocument d = QJsonDocument::fromJson(val.toUtf8()); QJsonObject jsonDoc = d.object(); - bool baseRecursive, baseShuffle, baseSorted; + bool baseRecursive = false, baseShuffle = false, baseSorted = false; SetJSONBool(baseRecursive, jsonDoc, "recursive"); SetJSONBool(baseShuffle, jsonDoc, "shuffle"); SetJSONBool(baseSorted, jsonDoc, "sorted"); @@ -258,6 +262,11 @@ AppConfig loadAppConfiguration(const AppConfig &commandLineConfig) { { entry.imageList = imageListString; } + std::string rssFeedURLString = ParseJSONString(jsonDoc, "redditrss"); + if(!rssFeedURLString.empty()) + { + entry.rssFeedURL = rssFeedURLString; + } loadedConfig.paths.append(entry); } loadedConfig.configPath = commandLineConfig.configPath; diff --git a/src/appconfig.h b/src/appconfig.h index 849de7b..324a055 100644 --- a/src/appconfig.h +++ b/src/appconfig.h @@ -18,6 +18,7 @@ struct Config { struct PathEntry { std::string path = ""; std::string imageList = ""; + std::string rssFeedURL = ""; bool exclusive = false; // only use this entry when it is valid, skip others bool recursive = false; @@ -37,7 +38,7 @@ struct PathEntry { return true; if(b.baseDisplayOptions.fitAspectAxisToWindow != baseDisplayOptions.fitAspectAxisToWindow) return true; - if (b.path != path || b.imageList != imageList) + if (b.path != path || b.imageList != imageList || b.rssFeedURL != rssFeedURL) return true; if (b.baseDisplayOptions.timeWindows.count() != baseDisplayOptions.timeWindows.count()) return true; diff --git a/src/imageselector.cpp b/src/imageselector.cpp index 147d94b..bbe6fc1 100644 --- a/src/imageselector.cpp +++ b/src/imageselector.cpp @@ -146,6 +146,9 @@ bool ImageSelector::imageInsideTimeWindow(const QVector &time bool ImageSelector::imageMatchesFilter(const ImageDetails& imageDetails) { + if(imageDetails.filename.find("https://") != std::string::npos) + return imageInsideTimeWindow(imageDetails.options.timeWindows); + if(!QFileInfo::exists(QString(imageDetails.filename.c_str()))) { if(debugMode) @@ -245,9 +248,16 @@ const ImageDetails ShuffleImageSelector::getNextImage(const ImageDisplayOptions { return imageDetails; } + bool bReloadedImages = false; imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(current_image_shuffle).toStdString()), baseOptions); current_image_shuffle = current_image_shuffle + 1; // ignore and move to next image while(!imageMatchesFilter(imageDetails)) { + if(current_image_shuffle >= images.size()) { + // don't keep looping + if(bReloadedImages == true) + return ImageDetails(); + bReloadedImages = true; + } reloadImagesIfNoneLeft(); imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(current_image_shuffle).toStdString()),baseOptions); current_image_shuffle = current_image_shuffle + 1; // ignore and move to next image @@ -300,8 +310,16 @@ const ImageDetails SortedImageSelector::getNextImage(const ImageDisplayOptions & { return imageDetails; } + bool bReloadedImages = false; imageDetails = populateImageDetails(pathTraverser->getImagePath(images.takeFirst().toStdString()), baseOptions); while(!imageMatchesFilter(imageDetails)) { + if (images.size() == 0) { + // don't keep looping + if(bReloadedImages == true) + return ImageDetails(); + bReloadedImages = true; + } + reloadImagesIfEmpty(); imageDetails = populateImageDetails(pathTraverser->getImagePath(images.takeFirst().toStdString()), baseOptions); } diff --git a/src/main.cpp b/src/main.cpp index 26ed635..da5a306 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "appconfig.h" #include +#include #include #include #include @@ -128,10 +129,14 @@ void ConfigureWindowFromSettings(MainWindow &w, const AppConfig &appConfig) w.setBaseOptions(appConfig.baseDisplayOptions); } -std::unique_ptr GetSelectorForConfig(const PathEntry& path, const bool debugMode) +std::unique_ptr GetSelectorForConfig(const PathEntry& path, QNetworkAccessManager& networkManagerIn, const bool debugMode) { std::unique_ptr pathTraverser; - if (!path.imageList.empty()) + if (!path.rssFeedURL.empty()) + { + pathTraverser = std::unique_ptr(new RedditRSSFeedPathTraverser(path.rssFeedURL, networkManagerIn, debugMode)); + } + else if (!path.imageList.empty()) { pathTraverser = std::unique_ptr(new ImageListPathTraverser(path.imageList, debugMode)); } @@ -161,18 +166,18 @@ std::unique_ptr GetSelectorForConfig(const PathEntry& path, const return selector; } -std::unique_ptr GetSelectorForApp(const AppConfig& appConfig) +std::unique_ptr GetSelectorForApp(const AppConfig& appConfig, QNetworkAccessManager& networkManagerIn) { if(appConfig.paths.count()==1) { - return GetSelectorForConfig(appConfig.paths[0], appConfig.debugMode); + return GetSelectorForConfig(appConfig.paths[0], networkManagerIn, appConfig.debugMode); } else { std::unique_ptr listSelector(new ListImageSelector()); for(const auto &path : appConfig.paths) { - auto selector = GetSelectorForConfig(path, appConfig.debugMode); + auto selector = GetSelectorForConfig(path, networkManagerIn, appConfig.debugMode); listSelector->AddImageSelector(selector, path.exclusive, path.baseDisplayOptions); } // new things @@ -181,7 +186,7 @@ std::unique_ptr GetSelectorForApp(const AppConfig& appConfig) } -void ReloadConfigIfNeeded(AppConfig &appConfig, MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector) +void ReloadConfigIfNeeded(AppConfig &appConfig, MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector, QNetworkAccessManager& networkManager) { QString jsonFile = getAppConfigFilePath(appConfig.configPath); QDir directory; @@ -198,7 +203,7 @@ void ReloadConfigIfNeeded(AppConfig &appConfig, MainWindow &w, ImageSwitcher *sw ConfigureWindowFromSettings(w, appConfig); if(appConfig.PathOptionsChanged(oldConfig)) { - std::unique_ptr selector = GetSelectorForApp(appConfig); + std::unique_ptr selector = GetSelectorForApp(appConfig, networkManager); switcher->setImageSelector(selector); } @@ -233,16 +238,19 @@ int main(int argc, char *argv[]) std::cout << "Overlay input: " << appConfig.overlay << std::endl; } + QNetworkAccessManager webCtrl; + MainWindow w; ConfigureWindowFromSettings(w, appConfig); + w.setNetworkManager(&webCtrl); w.show(); - std::unique_ptr selector = GetSelectorForApp(appConfig); + std::unique_ptr selector = GetSelectorForApp(appConfig, webCtrl); selector->setDebugMode(appConfig.debugMode); ImageSwitcher switcher(w, appConfig.rotationSeconds * 1000, selector); w.setImageSwitcher(&switcher); - std::function reloader = [&appConfig](MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector) { ReloadConfigIfNeeded(appConfig, w, switcher, selector); }; + std::function reloader = [&appConfig, &webCtrl](MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector) { ReloadConfigIfNeeded(appConfig, w, switcher, selector, webCtrl); }; switcher.setConfigFileReloader(reloader); switcher.start(); return a.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d6478f0..3f1323b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include MainWindow::MainWindow(QWidget *parent) : @@ -165,15 +166,46 @@ void MainWindow::checkWindowSize() void MainWindow::setImage(const ImageDetails &imageDetails) { currentImage = imageDetails; + downloadedData.clear(); + if (pendingReply) + { + pendingReply->abort(); + } updateImage(false); } +void MainWindow::fileDownloaded(QNetworkReply* netReply) +{ + if (netReply == pendingReply) + { + pendingReply = nullptr; + QNetworkReply::NetworkError err = netReply->error(); + if (err == QNetworkReply::NoError) + { + downloadedData = netReply->readAll(); + netReply->deleteLater(); + updateImage(false); + } + } +} + void MainWindow::updateImage(bool immediately) { checkWindowSize(); if (currentImage.filename == "") return; + if (currentImage.filename.find("https://") != std::string::npos && downloadedData.isNull()) + { + if (pendingReply == nullptr) + { + QNetworkRequest request(QUrl(currentImage.filename.c_str())); + pendingReply = networkManager->get(request); + connect( networkManager, SIGNAL (finished(QNetworkReply*)), this, SLOT (fileDownloaded(QNetworkReply*))); + } + return; + } + QLabel *label = this->findChild("image"); const QPixmap* oldImage = label->pixmap(); if (oldImage != NULL && !immediately) @@ -183,7 +215,28 @@ void MainWindow::updateImage(bool immediately) this->setPalette(palette); } - QPixmap p( currentImage.filename.c_str() ); + QPixmap p; + if (!downloadedData.isNull()) + { + p.loadFromData(downloadedData); + // BUG BUG have the selector update this? + currentImage.width = p.width(); + currentImage.height = p.height(); + currentImage.rotation = 0; + if (currentImage.width > currentImage.height) { + currentImage.aspect = ImageAspect_Landscape; + } else if (currentImage.height > currentImage.width) { + currentImage.aspect = ImageAspect_Portrait; + } else { + currentImage.aspect = ImageAspect_Any; + } + + } + else + { + p.load( currentImage.filename.c_str() ); + } + if(debugMode) { std::cout << "size:" << p.width() << "x" << p.height() << "(window:" << width() << "," << height() << ")" << std::endl; @@ -382,3 +435,8 @@ const ImageDisplayOptions &MainWindow::getBaseOptions() { return baseImageOptions; } + +void MainWindow::setNetworkManager(QNetworkAccessManager *networkManagerIn) +{ + networkManager = networkManagerIn; +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 1a3c8f5..3d569d7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -3,6 +3,7 @@ #include #include +#include #include "imagestructs.h" #include "imageselector.h" @@ -33,8 +34,11 @@ public: void setBaseOptions(const ImageDisplayOptions &baseOptionsIn); const ImageDisplayOptions &getBaseOptions(); void setImageSwitcher(ImageSwitcher *switcherIn); + void setNetworkManager(QNetworkAccessManager *networkManagerIn); public slots: void checkWindowSize(); +private slots: + void fileDownloaded(QNetworkReply* pReply); private: Ui::MainWindow *ui; @@ -43,6 +47,9 @@ private: ImageDisplayOptions baseImageOptions; bool imageAspectMatchesMonitor = false; ImageDetails currentImage; + QByteArray downloadedData; + QNetworkAccessManager *networkManager = nullptr; + QNetworkReply *pendingReply = nullptr; bool debugMode = false; QSize lastScreenSize = {0,0}; diff --git a/src/pathtraverser.cpp b/src/pathtraverser.cpp index d42385f..048c37a 100644 --- a/src/pathtraverser.cpp +++ b/src/pathtraverser.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include /* srand, rand */ @@ -108,5 +110,132 @@ ImageDisplayOptions ImageListPathTraverser::UpdateOptionsForImage(const std::str // no per file options modification supported Q_UNUSED(filename); Q_UNUSED(baseOptions); - return ImageDisplayOptions(); + return baseOptions; +} + + +RedditRSSFeedPathTraverser::RedditRSSFeedPathTraverser(const std::string& rssFeedURLIn, QNetworkAccessManager& networkManager, bool debugModeIn) : + PathTraverser("",debugModeIn), rssFeedURL(rssFeedURLIn), webCtrl(networkManager) +{ + connect( &webCtrl, SIGNAL (finished(QNetworkReply*)), this, SLOT (fileDownloaded(QNetworkReply*))); + RequestRSSFeed(); +} + +RedditRSSFeedPathTraverser::~RedditRSSFeedPathTraverser() +{ +} + +void RedditRSSFeedPathTraverser::RequestRSSFeed() +{ + if (pendingReply) + { + pendingReply->abort(); + } + if (debugMode) + { + std::cout << "Requesting RSS feed:" << rssFeedURL << std::endl; + } + rssRequestedTime = QDateTime::currentDateTime(); + QNetworkRequest request(QUrl(rssFeedURL.c_str())); + pendingReply = webCtrl.get(request); +} + +void RedditRSSFeedPathTraverser::fileDownloaded(QNetworkReply* netReply) +{ + if (netReply != pendingReply) + return; + + pendingReply = nullptr; + + QNetworkReply::NetworkError err = netReply->error(); + if (err != QNetworkReply::NoError) + { + std::cout << "Failed to load Reddit RSS URL: " << err << std::endl; + return; + } + + QString str (netReply->readAll()); + QVariant vt = netReply->attribute(QNetworkRequest::RedirectionTargetAttribute); + netReply->deleteLater(); + if (!vt.isNull()) + { + if (debugMode) + { + std::cout << "Redirected to:" << vt.toUrl().toString().toStdString() << std::endl; + } + webCtrl.get(QNetworkRequest(vt.toUrl())); + } + else + { + QDomDocument doc; + QString error; + if (!doc.setContent(str, false, &error)) + { + if (debugMode) + { + std::cout << "Failed to load page:" << error.toStdString() << std::endl; + } + } + else + { + QDomElement docElem = doc.documentElement(); + QDomNodeList nodeList = docElem.elementsByTagName("entry"); + for (int iEntry = 0; iEntry < nodeList.length(); ++iEntry) + { + QDomNode node = nodeList.item(iEntry); + QDomElement e = node.toElement(); + QDomNode contentNode = e.elementsByTagName("content").item(0).firstChild(); + QDomDocument docContent; + if (!docContent.setContent(contentNode.nodeValue(), false, &error)) + { + continue; + } + + QDomNodeList addressEntries = docContent.documentElement().elementsByTagName("a"); + for (int iAddr = 0; iAddr < addressEntries.length(); ++iAddr) + { + QDomNode node = addressEntries.item(iAddr); + /*QString output; + QTextStream stream(&output); + node.save(stream, 0); + qDebug() << "nodeValue: " << output;*/ + if (node.toElement().text() == "[link]" && node.hasAttributes() ) + { + QDomAttr a = node.toElement().attributeNode("href"); + // check if the URL matches one of our supported formats + for ( const QString& format : supportedFormats ) + { + if (a.value().endsWith(format)) + { + imageURLS.append(a.value()); + } + } + } + } + } + } + } +} + + +QStringList RedditRSSFeedPathTraverser::getImages() const +{ + // refresh the feed after 5 hours + if (rssRequestedTime.secsTo(QDateTime::currentDateTime()) > 60*60*5 ) + { + const_cast(this)->RequestRSSFeed(); + } + return imageURLS; +} + +const std::string RedditRSSFeedPathTraverser::getImagePath(const std::string image) const +{ + return image; +} + +ImageDisplayOptions RedditRSSFeedPathTraverser::UpdateOptionsForImage(const std::string& filename, const ImageDisplayOptions& baseOptions) const +{ + // no per file options modification supported + Q_UNUSED(filename); + return baseOptions; } diff --git a/src/pathtraverser.h b/src/pathtraverser.h index 7f4d921..9dc6425 100644 --- a/src/pathtraverser.h +++ b/src/pathtraverser.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "imageselector.h" static const QStringList supportedFormats={"jpg","jpeg","png","tif","tiff"}; @@ -58,4 +61,27 @@ class ImageListPathTraverser : public PathTraverser private: QStringList imageList; }; + +class RedditRSSFeedPathTraverser: public QObject, public PathTraverser +{ + Q_OBJECT + public: + RedditRSSFeedPathTraverser(const std::string& rSSFeedURL,QNetworkAccessManager& networkManager, bool debugModeIn); + virtual ~RedditRSSFeedPathTraverser(); + + virtual QStringList getImages() const; + virtual const std::string getImagePath(const std::string image) const; + virtual ImageDisplayOptions UpdateOptionsForImage(const std::string& filename, const ImageDisplayOptions& baseOptions) const; + + private slots: + void fileDownloaded(QNetworkReply* pReply); + private: + void RequestRSSFeed(); + std::string rssFeedURL; + QStringList imageURLS; + QNetworkAccessManager& webCtrl; + QNetworkReply *pendingReply = nullptr; + QDateTime rssRequestedTime; +}; + #endif // PATHTRAVERSER_H diff --git a/src/slide.pro b/src/slide.pro index 560414a..db060ef 100644 --- a/src/slide.pro +++ b/src/slide.pro @@ -4,8 +4,8 @@ # #------------------------------------------------- -QT += core gui -# CONFIG += qt debug +QT += core gui network xml +CONFIG += qt debug greaterThan(QT_MAJOR_VERSION, 4): QT += widgets