From ad9d566767c3e592f2de9b72e553597a2258b79d Mon Sep 17 00:00:00 2001 From: thunder2 Date: Tue, 9 May 2023 07:49:34 +0200 Subject: [PATCH] FeedReader: Added processing of enclosure in RSS feed --- plugins/FeedReader/gui/AddFeedDialog.cpp | 4 +- plugins/FeedReader/gui/FeedReaderDialog.cpp | 60 +++++++--- plugins/FeedReader/gui/FeedReaderDialog.h | 2 +- .../gui/FeedReaderMessageWidget.cpp | 102 +++++++++++++--- .../FeedReader/gui/FeedReaderMessageWidget.h | 2 + .../FeedReader/gui/FeedReaderMessageWidget.ui | 73 +++++++++-- plugins/FeedReader/gui/FeedReaderNotify.cpp | 4 +- plugins/FeedReader/gui/FeedReaderNotify.h | 4 +- plugins/FeedReader/interface/rsFeedReader.h | 19 ++- plugins/FeedReader/services/p3FeedReader.cc | 94 +++++++++++---- plugins/FeedReader/services/p3FeedReader.h | 10 +- .../FeedReader/services/p3FeedReaderThread.cc | 113 +++++++++++++++--- .../FeedReader/services/p3FeedReaderThread.h | 5 + .../FeedReader/services/rsFeedReaderItems.cc | 16 ++- .../FeedReader/services/rsFeedReaderItems.h | 6 + 15 files changed, 406 insertions(+), 108 deletions(-) diff --git a/plugins/FeedReader/gui/AddFeedDialog.cpp b/plugins/FeedReader/gui/AddFeedDialog.cpp index 4e0645607..59baf908a 100644 --- a/plugins/FeedReader/gui/AddFeedDialog.cpp +++ b/plugins/FeedReader/gui/AddFeedDialog.cpp @@ -100,7 +100,7 @@ AddFeedDialog::AddFeedDialog(RsFeedReader *feedReader, FeedReaderNotify *notify, ui->postedOnlyImageCheckBox->setEnabled(false); ui->postedOnlyImageCheckBox->setChecked(false); ui->postedShinkImageCheckBox->setEnabled(false); - ui->postedShinkImageCheckBox->setChecked(false); + ui->postedShinkImageCheckBox->setChecked(true); ui->useAuthenticationCheckBox->setChecked(false); ui->useStandardStorageTimeCheckBox->setChecked(true); ui->useStandardUpdateInterval->setChecked(true); @@ -199,11 +199,9 @@ void AddFeedDialog::postedFirstImageToggled() { bool checked = ui->postedFirstImageCheckBox->isChecked(); ui->postedOnlyImageCheckBox->setEnabled(checked); - ui->postedShinkImageCheckBox->setEnabled(checked); if (!checked) { ui->postedOnlyImageCheckBox->setChecked(false); - ui->postedShinkImageCheckBox->setChecked(false); } } diff --git a/plugins/FeedReader/gui/FeedReaderDialog.cpp b/plugins/FeedReader/gui/FeedReaderDialog.cpp index 7ea17983b..08f390a09 100644 --- a/plugins/FeedReader/gui/FeedReaderDialog.cpp +++ b/plugins/FeedReader/gui/FeedReaderDialog.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "FeedReaderDialog.h" #include "FeedReaderMessageWidget.h" @@ -36,6 +37,7 @@ #include "gui/notifyqt.h" #include "FeedReaderUserNotify.h" #include "gui/Posted/PostedCreatePostDialog.h" +#include "util/imageutil.h" #include "interface/rsFeedReader.h" #include "retroshare/rsiface.h" @@ -56,6 +58,8 @@ #define ROLE_FEED_ERROR Qt::UserRole + 9 #define ROLE_FEED_DEACTIVATED Qt::UserRole + 10 +const int MAXMESSAGESIZE = RsSerialiser::MAX_SERIAL_SIZE - 70000; + FeedReaderDialog::FeedReaderDialog(RsFeedReader *feedReader, FeedReaderNotify *notify, QWidget *parent) : MainPage(parent), mFeedReader(feedReader), mNotify(notify), ui(new Ui::FeedReaderDialog) { @@ -67,7 +71,7 @@ FeedReaderDialog::FeedReaderDialog(RsFeedReader *feedReader, FeedReaderNotify *n mMessageWidget = NULL; connect(mNotify, &FeedReaderNotify::feedChanged, this, &FeedReaderDialog::feedChanged, Qt::QueuedConnection); - connect(mNotify, &FeedReaderNotify::shrinkImage, this, &FeedReaderDialog::shrinkImage, Qt::QueuedConnection); + connect(mNotify, &FeedReaderNotify::optimizeImage, this, &FeedReaderDialog::optimizeImage, Qt::QueuedConnection); connect(NotifyQt::getInstance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); @@ -606,34 +610,52 @@ void FeedReaderDialog::feedChanged(uint32_t feedId, int type) calculateFeedItems(); } -void FeedReaderDialog::shrinkImage() +void FeedReaderDialog::optimizeImage() { while (true) { - FeedReaderShrinkImageTask *shrinkImageTask = mFeedReader->getShrinkImageTask(); + FeedReaderOptimizeImageTask *optimizeImageTask = mFeedReader->getOptimizeImageTask(); - if (!shrinkImageTask) { + if (!optimizeImageTask) { return; } - switch (shrinkImageTask->mType) { - case FeedReaderShrinkImageTask::POSTED: - { - QImage image; - if (image.loadFromData(shrinkImageTask->mImage.data(), shrinkImageTask->mImage.size())) { - QByteArray imageBytes; - QImage imageOpt; - if (PostedCreatePostDialog::optimizeImage(image, imageBytes, imageOpt)) { - shrinkImageTask->mImageResult.assign(imageBytes.begin(), imageBytes.end()); - shrinkImageTask->mResult = true; + optimizeImageTask->mResult = false; + + QImage image; + if (image.loadFromData(optimizeImageTask->mImage.data(), optimizeImageTask->mImage.size())) { + QByteArray optimizedImageData; + std::string optimizedImageMimeType; + QImage imageOptDummy; + + switch (optimizeImageTask->mType) { + case FeedReaderOptimizeImageTask::POSTED: + if (PostedCreatePostDialog::optimizeImage(image, optimizedImageData, imageOptDummy)) { + optimizedImageMimeType = "image/jpeg"; + optimizeImageTask->mResult = true; + } + break; + case FeedReaderOptimizeImageTask::SIZE: + if (ImageUtil::optimizeSizeBytes(optimizedImageData, image, imageOptDummy, "JPG", 0, MAXMESSAGESIZE)) { + optimizedImageMimeType = "image/jpg"; + optimizeImageTask->mResult = true; + } + break; + } + + if (optimizeImageTask->mResult) { + if (optimizedImageData.size() < (ssize_t) optimizeImageTask->mImage.size()) { + /* use optimized image */ + optimizeImageTask->mImageResult.assign(optimizedImageData.begin(), optimizedImageData.end()); + optimizeImageTask->mMimeTypeResult = optimizedImageMimeType; + } else { + /* use original image */ + optimizeImageTask->mImageResult = optimizeImageTask->mImage; + optimizeImageTask->mMimeTypeResult = optimizeImageTask->mMimeType; } } - break; - } - default: - shrinkImageTask->mResult = false; } - mFeedReader->setShrinkImageTaskResult(shrinkImageTask); + mFeedReader->setOptimizeImageTaskResult(optimizeImageTask); } } diff --git a/plugins/FeedReader/gui/FeedReaderDialog.h b/plugins/FeedReader/gui/FeedReaderDialog.h index b4854d2f3..12e11ee7f 100644 --- a/plugins/FeedReader/gui/FeedReaderDialog.h +++ b/plugins/FeedReader/gui/FeedReaderDialog.h @@ -69,7 +69,7 @@ private slots: /* FeedReaderNotify */ void feedChanged(uint32_t feedId, int type); - void shrinkImage(); + void optimizeImage(); private: uint32_t currentFeedId(); diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp b/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp index 5be5d3ebc..59f72103c 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "FeedReaderMessageWidget.h" #include "ui_FeedReaderMessageWidget.h" @@ -38,6 +39,8 @@ #include "util/QtVersion.h" #include "gui/Posted/PostedCreatePostDialog.h" #include "gui/gxsforums/CreateGxsForumMsg.h" +#include "gui/common/FilesDefs.h" +#include "util/imageutil.h" #include "retroshare/rsiface.h" #include "retroshare/rsgxsforums.h" @@ -87,6 +90,12 @@ FeedReaderMessageWidget::FeedReaderMessageWidget(uint32_t feedId, RsFeedReader * connect(ui->msgRemoveButton, SIGNAL(clicked()), this, SLOT(removeMsg())); connect(ui->feedProcessButton, SIGNAL(clicked()), this, SLOT(processFeed())); + /* Set initial size the splitter */ + QList sizes; + sizes << 250 << width(); // Qt calculates the right sizes + ui->pictureSplitter->setSizes(sizes); + ui->pictureSplitter->hide(); + // create timer for navigation mTimer = new QTimer(this); mTimer->setInterval(300); @@ -119,6 +128,10 @@ FeedReaderMessageWidget::FeedReaderMessageWidget(uint32_t feedId, RsFeedReader * ui->filterLineEdit->addFilter(QIcon(), tr("Author"), COLUMN_MSG_AUTHOR, tr("Search Author")); ui->filterLineEdit->setCurrentFilter(COLUMN_MSG_TITLE); + /* add image actions */ + ui->attachmentLabel->addContextMenuAction(ui->actionAttachmentCopyLinkLocation); + connect(ui->actionAttachmentCopyLinkLocation, &QAction::triggered, this, &FeedReaderMessageWidget::attachmentCopyLinkLocation); + /* load settings */ processSettings(true); @@ -179,6 +192,7 @@ void FeedReaderMessageWidget::processSettings(bool load) // state of splitter ui->msgSplitter->restoreState(Settings->value("msgSplitter").toByteArray()); + ui->pictureSplitter->restoreState(Settings->value("pictureSplitter").toByteArray()); } else { // save settings @@ -187,6 +201,7 @@ void FeedReaderMessageWidget::processSettings(bool load) // state of splitter Settings->setValue("msgSplitter", ui->msgSplitter->saveState()); + Settings->setValue("pictureSplitter", ui->pictureSplitter->saveState()); } Settings->endGroup(); @@ -599,6 +614,21 @@ void FeedReaderMessageWidget::msgItemChanged() mTimer->start(); } +void FeedReaderMessageWidget::clearMessage() +{ + ui->msgTitle->clear(); +// ui->msgLink->clear(); + ui->msgText->clear(); + ui->msgTextSplitter->clear(); + ui->attachmentLabel->clear(); + ui->linkButton->setEnabled(false); + ui->msgText->show(); + ui->pictureSplitter->hide(); + + ui->actionAttachmentCopyLinkLocation->setData(QVariant()); + ui->actionAttachmentCopyLinkLocation->setEnabled(false); +} + void FeedReaderMessageWidget::updateCurrentMessage() { mTimer->stop(); @@ -608,10 +638,7 @@ void FeedReaderMessageWidget::updateCurrentMessage() std::string msgId = currentMsgId(); if (mFeedId == 0 || msgId.empty()) { - ui->msgTitle->clear(); -// ui->msgLink->clear(); - ui->msgText->clear(); - ui->linkButton->setEnabled(false); + clearMessage(); ui->msgReadButton->setEnabled(false); ui->msgUnreadButton->setEnabled(false); @@ -622,10 +649,7 @@ void FeedReaderMessageWidget::updateCurrentMessage() QTreeWidgetItem *item = ui->msgTreeWidget->currentItem(); if (!item) { /* there is something wrong */ - ui->msgTitle->clear(); -// ui->msgLink->clear(); - ui->msgText->clear(); - ui->linkButton->setEnabled(false); + clearMessage(); ui->msgReadButton->setEnabled(false); ui->msgUnreadButton->setEnabled(false); @@ -640,10 +664,7 @@ void FeedReaderMessageWidget::updateCurrentMessage() /* get msg */ FeedMsgInfo msgInfo; if (!mFeedReader->getMsgInfo(mFeedId, msgId, msgInfo)) { - ui->msgTitle->clear(); -// ui->msgLink->clear(); - ui->msgText->clear(); - ui->linkButton->setEnabled(false); + clearMessage(); return; } @@ -663,9 +684,41 @@ void FeedReaderMessageWidget::updateCurrentMessage() setMsgAsReadUnread(row, setToReadOnActive); } + ui->actionAttachmentCopyLinkLocation->setData(QVariant()); + ui->actionAttachmentCopyLinkLocation->setEnabled(false); + + if (!msgInfo.attachment.empty()) { + QByteArray imageData((char*) msgInfo.attachment.data(), msgInfo.attachment.size()); + QPixmap pixmap; + if (pixmap.loadFromData(imageData)) { + ui->attachmentLabel->setPixmap(pixmap); + ui->pictureSplitter->show(); + ui->msgText->hide(); + } else { + ui->pictureSplitter->hide(); + ui->msgText->show(); + } + + if (!msgInfo.attachmentLink.empty()) { + ui->actionAttachmentCopyLinkLocation->setData(QString::fromUtf8(msgInfo.attachmentLink.c_str())); + ui->actionAttachmentCopyLinkLocation->setEnabled(true); + } + } else { + ui->pictureSplitter->hide(); + ui->msgText->show(); + } + QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8((msgInfo.descriptionTransformed.empty() ? msgInfo.description : msgInfo.descriptionTransformed).c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); - ui->msgText->setHtml(msgTxt); + if (ui->pictureSplitter->isVisible()) { + ui->msgTextSplitter->setHtml(msgTxt); + ui->msgText->clear(); + } else { + ui->msgText->setHtml(msgTxt); + ui->msgTextSplitter->clear(); + ui->attachmentLabel->clear(); + } + ui->msgTitle->setText(QString::fromUtf8(msgInfo.title.c_str())); ui->linkButton->setEnabled(!msgInfo.link.empty()); @@ -742,11 +795,11 @@ void FeedReaderMessageWidget::toggleMsgText() void FeedReaderMessageWidget::toggleMsgText_internal() { if (ui->expandButton->isChecked()) { - ui->msgText->setVisible(true); + ui->horizontalLayoutWidget->setVisible(true); ui->expandButton->setIcon(QIcon(QString(":/images/edit_remove24.png"))); ui->expandButton->setToolTip(tr("Hide")); } else { - ui->msgText->setVisible(false); + ui->horizontalLayoutWidget->setVisible(false); ui->expandButton->setIcon(QIcon(QString(":/images/edit_add24.png"))); ui->expandButton->setToolTip(tr("Expand")); } @@ -969,3 +1022,22 @@ void FeedReaderMessageWidget::addToPosted() msgDialog->setLink(QString::fromUtf8(msgInfo.link.c_str())); msgDialog->show(); } + +void FeedReaderMessageWidget::attachmentCopyLinkLocation() +{ + QAction *action = dynamic_cast(sender()) ; + if (!action) { + return; + } + + QVariant data = action->data(); + if (!data.isValid()) { + return; + } + + if (data.type() != QVariant::String) { + return; + } + + QApplication::clipboard()->setText(data.toString()); +} diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.h b/plugins/FeedReader/gui/FeedReaderMessageWidget.h index 317d61e71..1223b3b4e 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.h +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.h @@ -77,6 +77,7 @@ private slots: void fillPostedMenu(); void addToForum(); void addToPosted(); + void attachmentCopyLinkLocation(); /* FeedReaderNotify */ void feedChanged(uint32_t feedId, int type); @@ -92,6 +93,7 @@ private: void filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn); void filterItem(QTreeWidgetItem *item); void toggleMsgText_internal(); + void clearMessage(); bool mProcessSettings; RSTreeWidgetItemCompareRole *mMsgCompareRole; diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.ui b/plugins/FeedReader/gui/FeedReaderMessageWidget.ui index 3542c5375..fada5279e 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.ui +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.ui @@ -182,7 +182,7 @@ - + :/images/message-state-header.png:/images/message-state-header.png @@ -245,7 +245,7 @@ - + :/images/edit_remove24.png:/images/edit_remove24.png @@ -260,20 +260,63 @@ - - - - 0 - 10 - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - + + + + + + + 0 + 10 + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Qt::Horizontal + + + + + + + pictureLabel + + + Qt::AlignCenter + + + + + + + + + 0 + 10 + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + Copy Link Location + + @@ -297,10 +340,14 @@
gui/common/ElidedLabel.h
1
+ + AspectRatioPixmapLabel + QLabel +
util/AspectRatioPixmapLabel.h
+
- diff --git a/plugins/FeedReader/gui/FeedReaderNotify.cpp b/plugins/FeedReader/gui/FeedReaderNotify.cpp index 33b0c294a..95728b225 100644 --- a/plugins/FeedReader/gui/FeedReaderNotify.cpp +++ b/plugins/FeedReader/gui/FeedReaderNotify.cpp @@ -34,7 +34,7 @@ void FeedReaderNotify::notifyMsgChanged(uint32_t feedId, const std::string &msgI emit msgChanged(feedId, QString::fromStdString(msgId), type); } -void FeedReaderNotify::notifyShrinkImage() +void FeedReaderNotify::notifyOptimizeImage() { - emit shrinkImage(); + emit optimizeImage(); } diff --git a/plugins/FeedReader/gui/FeedReaderNotify.h b/plugins/FeedReader/gui/FeedReaderNotify.h index 18090e70c..5913f1985 100644 --- a/plugins/FeedReader/gui/FeedReaderNotify.h +++ b/plugins/FeedReader/gui/FeedReaderNotify.h @@ -34,12 +34,12 @@ public: /* RsFeedReaderNotify */ virtual void notifyFeedChanged(uint32_t feedId, int type); virtual void notifyMsgChanged(uint32_t feedId, const std::string &msgId, int type); - virtual void notifyShrinkImage(); + virtual void notifyOptimizeImage(); signals: void feedChanged(uint32_t feedId, int type); void msgChanged(uint32_t feedId, const QString &msgId, int type); - void shrinkImage(); + void optimizeImage(); }; #endif diff --git a/plugins/FeedReader/interface/rsFeedReader.h b/plugins/FeedReader/interface/rsFeedReader.h index 72a900654..326d3e022 100644 --- a/plugins/FeedReader/interface/rsFeedReader.h +++ b/plugins/FeedReader/interface/rsFeedReader.h @@ -185,6 +185,9 @@ public: std::string description; std::string descriptionTransformed; time_t pubDate; + std::string attachmentLink; + std::vector attachment; + std::string attachmentMimeType; struct { bool isnew : 1; @@ -193,24 +196,28 @@ public: } flag; }; -class FeedReaderShrinkImageTask +class FeedReaderOptimizeImageTask { public: enum Type { - POSTED + POSTED, + SIZE }; public: Type mType; std::vector mImage; + std::string mMimeType; std::vector mImageResult; + std::string mMimeTypeResult; bool mResult; public: - FeedReaderShrinkImageTask(Type type, const std::vector &image) + FeedReaderOptimizeImageTask(Type type, const std::vector &image, const std::string &mimeType) { mType = type; mImage = image; + mMimeType = mimeType; mResult = false; } }; @@ -222,7 +229,7 @@ public: virtual void notifyFeedChanged(uint32_t /*feedId*/, int /*type*/) {} virtual void notifyMsgChanged(uint32_t /*feedId*/, const std::string &/*msgId*/, int /*type*/) {} - virtual void notifyShrinkImage() {} + virtual void notifyOptimizeImage() {} }; class RsFeedReader @@ -268,8 +275,8 @@ public: virtual bool getForumGroups(std::vector &groups, bool onlyOwn) = 0; virtual bool getPostedGroups(std::vector &groups, bool onlyOwn) = 0; - virtual FeedReaderShrinkImageTask *getShrinkImageTask() = 0; - virtual void setShrinkImageTaskResult(FeedReaderShrinkImageTask *shrinkImageTask) = 0; + virtual FeedReaderOptimizeImageTask *getOptimizeImageTask() = 0; + virtual void setOptimizeImageTaskResult(FeedReaderOptimizeImageTask *optimizeImageTask) = 0; virtual RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString) = 0; virtual RsFeedReaderErrorState processXslt(const std::string &xslt, std::string &description, std::string &errorString) = 0; diff --git a/plugins/FeedReader/services/p3FeedReader.cc b/plugins/FeedReader/services/p3FeedReader.cc index 6612210a7..3e46a027c 100644 --- a/plugins/FeedReader/services/p3FeedReader.cc +++ b/plugins/FeedReader/services/p3FeedReader.cc @@ -233,6 +233,13 @@ static void feedMsgToInfo(const RsFeedReaderMsg *msg, FeedMsgInfo &info) info.description = msg->description; info.descriptionTransformed = msg->descriptionTransformed; info.pubDate = msg->pubDate; + info.attachmentLink = msg->attachmentLink; + if (!msg->attachment.empty()) { + p3FeedReaderThread::fromBase64(msg->attachment, info.attachment); + } else { + info.attachment.clear(); + } + info.attachmentMimeType = msg->attachmentMimeType; info.flag.isnew = (msg->flag & RS_FEEDMSG_FLAG_NEW); info.flag.read = (msg->flag & RS_FEEDMSG_FLAG_READ); @@ -1264,7 +1271,7 @@ bool p3FeedReader::setMessageRead(uint32_t feedId, const std::string &msgId, boo } if (changed) { - IndicateConfigChanged(RsConfigMgr::CheckPriority::SAVE_NOW); + IndicateConfigChanged(RsConfigMgr::CheckPriority::SAVE_OFTEN); if (mNotify) { mNotify->notifyFeedChanged(feedId, NOTIFY_TYPE_MOD); mNotify->notifyMsgChanged(feedId, msgId, NOTIFY_TYPE_MOD); @@ -1450,19 +1457,19 @@ int p3FeedReader::tick() } // check images - bool imageToShrink = false; + bool imageToOptimze = false; { RsStackMutex stack(mImageMutex); /******* LOCK STACK MUTEX *********/ - imageToShrink = !mImages.empty(); + imageToOptimze = !mImages.empty(); } if (mNotify) { for (it = notifyIds.begin(); it != notifyIds.end(); ++it) { mNotify->notifyFeedChanged(*it, NOTIFY_TYPE_MOD); } - if (imageToShrink) { - mNotify->notifyShrinkImage(); + if (imageToOptimze) { + mNotify->notifyOptimizeImage(); } } @@ -2110,6 +2117,9 @@ void p3FeedReader::onProcessSuccess_addMsgs(uint32_t feedId, std::listdescription.clear(); miNew->descriptionTransformed.clear(); + miNew->attachmentLink.clear(); + miNew->attachment.clear(); + miNew->attachmentMimeType.clear(); } else { miNew->flag = RS_FEEDMSG_FLAG_NEW; addedMsgs.push_back(miNew->msgId); @@ -2151,6 +2161,25 @@ void p3FeedReader::onProcessSuccess_addMsgs(uint32_t feedId, std::list" + mi.link + ""; } + + if (!mi.attachmentBinary.empty()) { + if (p3FeedReaderThread::isContentType(mi.attachmentMimeType, "image/")) { + /* add attachement to description */ + + // optimize image + std::vector optimizedImage; + std::string optimizedMimeType; + if (optimizeImage(FeedReaderOptimizeImageTask::SIZE, mi.attachmentBinary, mi.attachmentBinaryMimeType, optimizedImage, optimizedMimeType)) { + std::string base64; + if (p3FeedReaderThread::toBase64(optimizedImage, base64)) { + std::string imageBase64; + rs_sprintf(imageBase64, "data:%s;base64,%s", optimizedMimeType.c_str(), base64.c_str()); + description += "
"; + } + } + } + } + forumMsg.mMsg = description; uint32_t token; @@ -2197,10 +2226,11 @@ void p3FeedReader::onProcessSuccess_addMsgs(uint32_t feedId, std::list shrinkedImage; - if (shrinkImage(FeedReaderShrinkImageTask::POSTED, mi.postedFirstImage, shrinkedImage)) { - postedPost.mImage.copy(shrinkedImage.data(), shrinkedImage.size()); + // optimize image + std::vector optimizedImage; + std::string optimizedMimeType; + if (optimizeImage(FeedReaderOptimizeImageTask::POSTED, mi.postedFirstImage, mi.postedFirstImageMimeType, optimizedImage, optimizedMimeType)) { + postedPost.mImage.copy(optimizedImage.data(), optimizedImage.size()); } } else { postedPost.mImage.copy(mi.postedFirstImage.data(), mi.postedFirstImage.size()); @@ -2219,6 +2249,23 @@ void p3FeedReader::onProcessSuccess_addMsgs(uint32_t feedId, std::list optimizedImage; + std::string optimizedMimeType; + if (optimizeImage(FeedReaderOptimizeImageTask::POSTED, mi.attachmentBinary, mi.attachmentBinaryMimeType, optimizedImage, optimizedMimeType)) { + postedPost.mImage.copy(optimizedImage.data(), optimizedImage.size()); + } + } else { + postedPost.mImage.copy(mi.attachmentBinary.data(), mi.attachmentBinary.size()); + } + } + } } postedPost.mNotes = description; @@ -2662,17 +2709,17 @@ bool p3FeedReader::getPostedGroups(std::vector &groups, bool only return true; } -bool p3FeedReader::shrinkImage(FeedReaderShrinkImageTask::Type type, const std::vector &image, std::vector &resultImage) +bool p3FeedReader::optimizeImage(FeedReaderOptimizeImageTask::Type type, const std::vector &image, const std::string &mimeType, std::vector &resultImage, std::string &resultMimeType) { if (!mNotify) { return false; } - FeedReaderShrinkImageTask *shrinkImageTask = new FeedReaderShrinkImageTask(type, image); + FeedReaderOptimizeImageTask *optimizeImageTask = new FeedReaderOptimizeImageTask(type, image, mimeType); { RsStackMutex stack(mImageMutex); /******* LOCK STACK MUTEX *********/ - mImages.push_back(shrinkImageTask); + mImages.push_back(optimizeImageTask); } /* Wait until task is complete */ @@ -2686,11 +2733,11 @@ bool p3FeedReader::shrinkImage(FeedReaderShrinkImageTask::Type type, const std:: if (++nSeconds >= 30) { // timeout - std::list::iterator it = std::find(mImages.begin(), mImages.end(), shrinkImageTask); + std::list::iterator it = std::find(mImages.begin(), mImages.end(), optimizeImageTask); if (it != mImages.end()) { mImages.erase(it); - delete(shrinkImageTask); + delete(optimizeImageTask); return false; } @@ -2701,16 +2748,17 @@ bool p3FeedReader::shrinkImage(FeedReaderShrinkImageTask::Type type, const std:: { RsStackMutex stack(mImageMutex); /******* LOCK STACK MUTEX *********/ - std::list::iterator it = std::find(mResultImages.begin(), mResultImages.end(), shrinkImageTask); + std::list::iterator it = std::find(mResultImages.begin(), mResultImages.end(), optimizeImageTask); if (it != mResultImages.end()) { mResultImages.erase(it); - bool result = shrinkImageTask->mResult; + bool result = optimizeImageTask->mResult; if (result) { - resultImage = shrinkImageTask->mImageResult; + resultImage = optimizeImageTask->mImageResult; + resultMimeType = optimizeImageTask->mMimeTypeResult; } - delete(shrinkImageTask); + delete(optimizeImageTask); return result; } @@ -2720,7 +2768,7 @@ bool p3FeedReader::shrinkImage(FeedReaderShrinkImageTask::Type type, const std:: return false; } -FeedReaderShrinkImageTask *p3FeedReader::getShrinkImageTask() +FeedReaderOptimizeImageTask *p3FeedReader::getOptimizeImageTask() { RsStackMutex stack(mImageMutex); /******* LOCK STACK MUTEX *********/ @@ -2728,19 +2776,19 @@ FeedReaderShrinkImageTask *p3FeedReader::getShrinkImageTask() return NULL; } - FeedReaderShrinkImageTask *imageResize = mImages.front(); + FeedReaderOptimizeImageTask *imageResize = mImages.front(); mImages.pop_front(); return imageResize; } -void p3FeedReader::setShrinkImageTaskResult(FeedReaderShrinkImageTask *shrinkImageTask) +void p3FeedReader::setOptimizeImageTaskResult(FeedReaderOptimizeImageTask *optimizeImageTask) { - if (!shrinkImageTask) { + if (!optimizeImageTask) { return; } RsStackMutex stack(mImageMutex); /******* LOCK STACK MUTEX *********/ - mResultImages.push_back(shrinkImageTask); + mResultImages.push_back(optimizeImageTask); } diff --git a/plugins/FeedReader/services/p3FeedReader.h b/plugins/FeedReader/services/p3FeedReader.h index bc20a8618..0df9bf79e 100644 --- a/plugins/FeedReader/services/p3FeedReader.h +++ b/plugins/FeedReader/services/p3FeedReader.h @@ -80,8 +80,8 @@ public: virtual bool getForumGroups(std::vector &groups, bool onlyOwn); virtual bool getPostedGroups(std::vector &groups, bool onlyOwn); - virtual FeedReaderShrinkImageTask *getShrinkImageTask(); - virtual void setShrinkImageTaskResult(FeedReaderShrinkImageTask *shrinkedImageTask); + virtual FeedReaderOptimizeImageTask *getOptimizeImageTask(); + virtual void setOptimizeImageTaskResult(FeedReaderOptimizeImageTask *optimizeImageTask); virtual RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString); virtual RsFeedReaderErrorState processXslt(const std::string &xslt, std::string &description, std::string &errorString); @@ -107,7 +107,7 @@ public: bool getPostedGroup(const RsGxsGroupId &groupId, RsPostedGroup &postedGroup); bool updatePostedGroup(const RsPostedGroup &postedGroup, const std::string &groupName, const std::string &groupDescription); bool waitForToken(RsGxsIfaceHelper *interface, uint32_t token); - bool shrinkImage(FeedReaderShrinkImageTask::Type type, const std::vector &image, std::vector &resultImage); + bool optimizeImage(FeedReaderOptimizeImageTask::Type type, const std::vector &image, const std::string &mimeType, std::vector &resultImage, std::string &resultMimeType); protected: /****************** p3Config STUFF *******************/ @@ -150,8 +150,8 @@ private: std::list mProcessFeeds; RsMutex mImageMutex; - std::list mImages; - std::list mResultImages; + std::list mImages; + std::list mResultImages; RsMutex mPreviewMutex; p3FeedReaderThread *mPreviewDownloadThread; diff --git a/plugins/FeedReader/services/p3FeedReaderThread.cc b/plugins/FeedReader/services/p3FeedReaderThread.cc index 5ba3ade35..01e449323 100644 --- a/plugins/FeedReader/services/p3FeedReaderThread.cc +++ b/plugins/FeedReader/services/p3FeedReaderThread.cc @@ -89,7 +89,7 @@ void p3FeedReaderThread::threadTick() /* first, filter the messages */ mFeedReader->onProcessSuccess_filterMsg(feed.feedId, msgs); if (isRunning()) { - /* second, process the descriptions */ + /* second, process the descriptions and attachment */ for (it = msgs.begin(); it != msgs.end(); ) { if (!isRunning()) { break; @@ -153,12 +153,12 @@ void p3FeedReaderThread::threadTick() /****************************** Download ***********************************/ /***************************************************************************/ -static bool isContentType(const std::string &contentType, const char *type) +bool p3FeedReaderThread::isContentType(const std::string &contentType, const char *type) { return (strncasecmp(contentType.c_str(), type, strlen(type)) == 0); } -static bool toBase64(const std::vector &data, std::string &base64) +bool p3FeedReaderThread::toBase64(const std::vector &data, std::string &base64) { bool result = false; @@ -187,6 +187,28 @@ static bool toBase64(const std::vector &data, std::string &base64 return result; } +bool p3FeedReaderThread::fromBase64(const std::string &base64, std::vector &data) +{ + bool result = false; + + BIO *b64 = BIO_new(BIO_f_base64()); + if (b64) { + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + BIO *source = BIO_new_mem_buf(base64.c_str(), -1); // read-only source + if (source) { + BIO_push(b64, source); + const int maxlen = base64.length() / 4 * 3 + 1; + data.resize(maxlen); + const int len = BIO_read(b64, data.data(), maxlen); + data.resize(len); + result = true; + } + BIO_free_all(b64); + } + + return result; +} + static std::string getBaseLink(std::string link) { size_t found = link.rfind('/'); @@ -254,12 +276,12 @@ static bool getFavicon(CURLWrapper &CURL, const std::string &url, std::string &i if (code == CURLE_OK) { if (CURL.responseCode() == 200) { std::string contentType = CURL.contentType(); - if (isContentType(contentType, "image/") || - isContentType(contentType, "application/octet-stream") || - isContentType(contentType, "text/plain")) { + if (p3FeedReaderThread::isContentType(contentType, "image/") || + p3FeedReaderThread::isContentType(contentType, "application/octet-stream") || + p3FeedReaderThread::isContentType(contentType, "text/plain")) { if (!vicon.empty()) { #warning p3FeedReaderThread.cc TODO thunder2: check it - result = toBase64(vicon, icon); + result = p3FeedReaderThread::toBase64(vicon, icon); } } } @@ -971,6 +993,19 @@ RsFeedReaderErrorState p3FeedReaderThread::process(const RsFeedReaderFeed &feed, item->pubDate = time(NULL); } + if (feedFormat == FORMAT_RSS) { + /* */ + xmlNodePtr enclosure = xml.findNode(node->children, "enclosure", false); + if (enclosure) { + std::string enclosureMimeType = xml.getAttr(enclosure, "type"); + std::string enclosureUrl = xml.getAttr(enclosure, "url"); + if (!enclosureUrl.empty()) { + item->attachmentLink = enclosureUrl; + item->attachmentMimeType = enclosureMimeType; + } + } + } + entries.push_back(item); } } else { @@ -1025,6 +1060,40 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe RsFeedReaderErrorState result = RS_FEED_ERRORSTATE_OK; std::string proxy = getProxyForFeed(feed); + /* attachment */ + if (!msg->attachmentLink.empty()) { + if (isContentType(msg->attachmentMimeType, "image/")) { + CURLWrapper CURL(proxy); + CURLcode code = CURL.downloadBinary(msg->attachmentLink, msg->attachmentBinary); + if (code == CURLE_OK && CURL.responseCode() == 200) { + std::string contentType = CURL.contentType(); + if (isContentType(contentType, "image/")) { + msg->attachmentBinaryMimeType = contentType; + + bool forum = (feed.flag & RS_FEED_FLAG_FORUM) && !feed.preview; + bool posted = (feed.flag & RS_FEED_FLAG_POSTED) && !feed.preview; + + if (!forum && ! posted) { + /* no need to optimize image */ + std::vector optimizedBinary; + std::string optimizedMimeType; + if (mFeedReader->optimizeImage(FeedReaderOptimizeImageTask::SIZE, msg->attachmentBinary, msg->attachmentBinaryMimeType, optimizedBinary, optimizedMimeType)) { + if (toBase64(optimizedBinary, msg->attachment)) { + msg->attachmentMimeType = optimizedMimeType; + } else { + msg->attachment.clear(); + } + } + } + } else { + msg->attachmentBinary.clear(); + } + } else { + msg->attachmentBinary.clear(); + } + } + } + std::string url; if (feed.flag & RS_FEED_FLAG_SAVE_COMPLETE_PAGE) { #ifdef FEEDREADER_DEBUG @@ -1083,6 +1152,10 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe if (isRunning()) { /* process description */ bool processPostedFirstImage = (feed.flag & RS_FEED_FLAG_POSTED_FIRST_IMAGE) ? TRUE : FALSE; + if (!msg->attachmentBinary.empty()) { + /* use attachment as image */ + processPostedFirstImage = FALSE; + } //long todo; // encoding HTMLWrapper html; @@ -1215,20 +1288,24 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe if (code == CURLE_OK && CURL.responseCode() == 200) { std::string contentType = CURL.contentType(); if (isContentType(contentType, "image/")) { - std::string base64; - if (toBase64(data, base64)) { - std::string imageBase64; - rs_sprintf(imageBase64, "data:%s;base64,%s", contentType.c_str(), base64.c_str()); - if (html.setAttr(node, "src", imageBase64.c_str())) { - removeImage = false; - - if (processPostedFirstImage && postedFirstImageNode == NULL) { - /* set first image */ - msg->postedFirstImage = data; - postedFirstImageNode = node; + std::vector optimizedData; + std::string optimizedMimeType; + if (mFeedReader->optimizeImage(FeedReaderOptimizeImageTask::SIZE, data, contentType, optimizedData, optimizedMimeType)) { + std::string base64; + if (toBase64(optimizedData, base64)) { + std::string imageBase64; + rs_sprintf(imageBase64, "data:%s;base64,%s", optimizedMimeType.c_str(), base64.c_str()); + if (html.setAttr(node, "src", imageBase64.c_str())) { + removeImage = false; } } } + if (processPostedFirstImage && postedFirstImageNode == NULL) { + /* set first image */ + msg->postedFirstImage = data; + msg->postedFirstImageMimeType = contentType; + postedFirstImageNode = node; + } } } } diff --git a/plugins/FeedReader/services/p3FeedReaderThread.h b/plugins/FeedReader/services/p3FeedReaderThread.h index 48d920dd6..a9be84010 100644 --- a/plugins/FeedReader/services/p3FeedReaderThread.h +++ b/plugins/FeedReader/services/p3FeedReaderThread.h @@ -54,6 +54,11 @@ public: static RsFeedReaderErrorState processXslt(const std::string &xslt, HTMLWrapper &html, std::string &errorString); static RsFeedReaderErrorState processTransformation(const RsFeedReaderFeed &feed, RsFeedReaderMsg *msg, std::string &errorString); + + static bool isContentType(const std::string &contentType, const char *type); + static bool toBase64(const std::vector &data, std::string &base64); + static bool fromBase64(const std::string &base64, std::vector &data); + private: virtual void threadTick() override; /// @see RsTickingThread diff --git a/plugins/FeedReader/services/rsFeedReaderItems.cc b/plugins/FeedReader/services/rsFeedReaderItems.cc index 5a2ad4fe1..0d5b1f60b 100644 --- a/plugins/FeedReader/services/rsFeedReaderItems.cc +++ b/plugins/FeedReader/services/rsFeedReaderItems.cc @@ -274,6 +274,9 @@ void RsFeedReaderMsg::clear() descriptionTransformed.clear(); pubDate = 0; flag = 0; + attachmentLink.clear(); + attachment.clear(); + attachmentMimeType.clear(); } std::ostream &RsFeedReaderMsg::print(std::ostream &out, uint16_t /*indent*/) @@ -294,6 +297,9 @@ uint32_t RsFeedReaderSerialiser::sizeMsg(RsFeedReaderMsg *item) s += GetTlvStringSize(item->descriptionTransformed); s += sizeof(uint32_t); /* pubDate */ s += sizeof(uint32_t); /* flag */ + s += GetTlvStringSize(item->attachmentLink); + s += GetTlvStringSize(item->attachment); + s += GetTlvStringSize(item->attachmentMimeType); return s; } @@ -317,7 +323,7 @@ bool RsFeedReaderSerialiser::serialiseMsg(RsFeedReaderMsg *item, void *data, uin offset += 8; /* add values */ - ok &= setRawUInt16(data, tlvsize, &offset, 2); /* version */ + ok &= setRawUInt16(data, tlvsize, &offset, 3); /* version */ ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_GENID, item->msgId); ok &= setRawUInt32(data, tlvsize, &offset, item->feedId); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, item->title); @@ -327,6 +333,9 @@ bool RsFeedReaderSerialiser::serialiseMsg(RsFeedReaderMsg *item, void *data, uin ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_COMMENT, item->descriptionTransformed); ok &= setRawUInt32(data, tlvsize, &offset, item->pubDate); ok &= setRawUInt32(data, tlvsize, &offset, item->flag); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_LOCATION, item->attachmentLink); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_PIC_AUTH, item->attachment); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_PIC_TYPE, item->attachmentMimeType); if (offset != tlvsize) { @@ -390,6 +399,11 @@ RsFeedReaderMsg *RsFeedReaderSerialiser::deserialiseMsg(void *data, uint32_t *pk } ok &= getRawUInt32(data, rssize, &offset, (uint32_t*) &(item->pubDate)); ok &= getRawUInt32(data, rssize, &offset, &(item->flag)); + if (version >= 3) { + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_LOCATION, item->attachmentLink); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_PIC_AUTH, item->attachment); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_PIC_TYPE, item->attachmentMimeType); + } if (offset != rssize) { diff --git a/plugins/FeedReader/services/rsFeedReaderItems.h b/plugins/FeedReader/services/rsFeedReaderItems.h index 3adafd55a..4e343be75 100644 --- a/plugins/FeedReader/services/rsFeedReaderItems.h +++ b/plugins/FeedReader/services/rsFeedReaderItems.h @@ -124,9 +124,15 @@ public: std::string descriptionTransformed; time_t pubDate; uint32_t flag; // RS_FEEDMSG_FLAG_... + std::string attachmentLink; + std::string attachment; // binary as base64 + std::string attachmentMimeType; // Only in memory when receiving messages + std::vector attachmentBinary; + std::string attachmentBinaryMimeType; std::vector postedFirstImage; + std::string postedFirstImageMimeType; std::string postedDescriptionWithoutFirstImage; };