- Add the ability to parse the RSS feeds from reddit groups (in particular the image feed groups like EarthPorn) and display them

This commit is contained in:
Alfred Reynolds
2021-08-22 15:10:26 +12:00
parent 3557b6041f
commit 833e7ef915
9 changed files with 271 additions and 15 deletions

View File

@@ -180,6 +180,10 @@ QVector<PathEntry> 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;

View File

@@ -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;

View File

@@ -146,6 +146,9 @@ bool ImageSelector::imageInsideTimeWindow(const QVector<DisplayTimeWindow> &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);
}

View File

@@ -6,6 +6,7 @@
#include "appconfig.h"
#include <QApplication>
#include <QNetworkAccessManager>
#include <iostream>
#include <sys/file.h>
#include <errno.h>
@@ -128,10 +129,14 @@ void ConfigureWindowFromSettings(MainWindow &w, const AppConfig &appConfig)
w.setBaseOptions(appConfig.baseDisplayOptions);
}
std::unique_ptr<ImageSelector> GetSelectorForConfig(const PathEntry& path, const bool debugMode)
std::unique_ptr<ImageSelector> GetSelectorForConfig(const PathEntry& path, QNetworkAccessManager& networkManagerIn, const bool debugMode)
{
std::unique_ptr<PathTraverser> pathTraverser;
if (!path.imageList.empty())
if (!path.rssFeedURL.empty())
{
pathTraverser = std::unique_ptr<PathTraverser>(new RedditRSSFeedPathTraverser(path.rssFeedURL, networkManagerIn, debugMode));
}
else if (!path.imageList.empty())
{
pathTraverser = std::unique_ptr<PathTraverser>(new ImageListPathTraverser(path.imageList, debugMode));
}
@@ -161,18 +166,18 @@ std::unique_ptr<ImageSelector> GetSelectorForConfig(const PathEntry& path, const
return selector;
}
std::unique_ptr<ImageSelector> GetSelectorForApp(const AppConfig& appConfig)
std::unique_ptr<ImageSelector> 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<ListImageSelector> 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<ImageSelector> 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<ImageSelector> selector = GetSelectorForApp(appConfig);
std::unique_ptr<ImageSelector> 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<ImageSelector> selector = GetSelectorForApp(appConfig);
std::unique_ptr<ImageSelector> selector = GetSelectorForApp(appConfig, webCtrl);
selector->setDebugMode(appConfig.debugMode);
ImageSwitcher switcher(w, appConfig.rotationSeconds * 1000, selector);
w.setImageSwitcher(&switcher);
std::function<void(MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector)> reloader = [&appConfig](MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector) { ReloadConfigIfNeeded(appConfig, w, switcher, selector); };
std::function<void(MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector)> reloader = [&appConfig, &webCtrl](MainWindow &w, ImageSwitcher *switcher, ImageSelector *selector) { ReloadConfigIfNeeded(appConfig, w, switcher, selector, webCtrl); };
switcher.setConfigFileReloader(reloader);
switcher.start();
return a.exec();

View File

@@ -17,6 +17,7 @@
#include <QGraphicsPixmapItem>
#include <QApplication>
#include <QScreen>
#include <QNetworkReply>
#include <sstream>
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<QLabel*>("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;
}

View File

@@ -3,6 +3,7 @@
#include <QMainWindow>
#include <QPixmap>
#include <QNetworkAccessManager>
#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};

View File

@@ -5,6 +5,8 @@
#include <QDirIterator>
#include <QDir>
#include <QFileInfo>
#include <QDomDocument>
#include <QDomAttr>
#include <iostream>
#include <stdlib.h> /* 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<RedditRSSFeedPathTraverser *>(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;
}

View File

@@ -4,6 +4,9 @@
#include <iostream>
#include <QDir>
#include <QStringList>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#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

View File

@@ -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