- 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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
26
src/main.cpp
26
src/main.cpp
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user