Line data Source code
1 : #include "RawFeedRewriter.h"
2 :
3 : #include <QDebug>
4 : #include <QtConcurrent>
5 :
6 : #include "QImageCache.h"
7 :
8 32 : RawFeedRewriter::RawFeedRewriter(QObject *parent, QNetworkAccessManager* networkManager) :
9 : QObject(parent),
10 32 : newsList(nullptr),
11 32 : sanitizer(),
12 32 : imageGrabber(nullptr, networkManager)
13 : {
14 32 : connect(&imageGrabber, &ImageGrabber::finished, this, &RawFeedRewriter::onImageGrabberFinished);
15 32 : connect(&sanitizeWatcher, &QFutureWatcher<QSet<QUrl>>::finished, this, &RawFeedRewriter::onSanitizeFinished);
16 40 : connect(&finalizeWatcher, &QFutureWatcher<void>::finished, this, [this]() { emit finished(); });
17 32 : }
18 :
19 32 : void RawFeedRewriter::rewrite(QList<std::shared_ptr<RawNews>> *newsList)
20 : {
21 32 : this->newsList = newsList;
22 :
23 : // Reset sanitizer state for new batch.
24 32 : sanitizer.reset();
25 :
26 : // First pass: sanitize all HTML and collect image URLs.
27 : // Run on a background thread to avoid blocking the UI.
28 0 : auto future = QtConcurrent::run([this]() -> QSet<QUrl> {
29 32 : QSet<QUrl> allImageURLs;
30 :
31 64 : for (const auto& news : *this->newsList) {
32 32 : QSet<QUrl> imageURLs;
33 :
34 32 : if (!news->content.isEmpty()) {
35 0 : news->content = sanitizer.sanitize(news->content, imageURLs);
36 0 : allImageURLs.unite(imageURLs);
37 : }
38 :
39 32 : if (!news->description.isEmpty()) {
40 31 : news->description = sanitizer.sanitize(news->description, imageURLs);
41 31 : allImageURLs.unite(imageURLs);
42 : }
43 :
44 : // Include media image URLs for downloading and caching.
45 32 : if (!news->mediaImageURL.isEmpty()) {
46 0 : allImageURLs.insert(QUrl(news->mediaImageURL));
47 : }
48 32 : }
49 :
50 32 : return allImageURLs;
51 32 : });
52 32 : sanitizeWatcher.setFuture(future);
53 32 : }
54 :
55 32 : void RawFeedRewriter::onSanitizeFinished()
56 : {
57 32 : QSet<QUrl> allImageURLs = sanitizeWatcher.result();
58 :
59 : // No images? Finalize and we're done.
60 32 : if (allImageURLs.isEmpty()) {
61 24 : finalizeAll();
62 24 : emit finished();
63 24 : return;
64 : }
65 :
66 : // Fetch images, then finalize when done.
67 8 : imageGrabber.fetchUrls(allImageURLs.values());
68 32 : }
69 :
70 8 : void RawFeedRewriter::onImageGrabberFinished()
71 : {
72 : // Run finalization on a background thread to avoid blocking the UI
73 : // during XML parsing and image caching.
74 0 : auto future = QtConcurrent::run([this]() {
75 8 : finalizeAll();
76 8 : });
77 8 : finalizeWatcher.setFuture(future);
78 8 : }
79 :
80 32 : void RawFeedRewriter::finalizeAll()
81 : {
82 32 : QMap<QUrl, ImageData> imageResults = *imageGrabber.getResults();
83 :
84 64 : for (const auto& news : *newsList) {
85 32 : if (!news->content.isEmpty()) {
86 0 : news->content = sanitizer.finalize(news->content, imageResults);
87 : }
88 :
89 32 : if (!news->description.isEmpty()) {
90 31 : news->description = sanitizer.finalize(news->description, imageResults);
91 : }
92 :
93 : // Cache media image and store cached path with dimensions.
94 32 : if (!news->mediaImageURL.isEmpty()) {
95 0 : QUrl mediaUrl(news->mediaImageURL);
96 0 : if (imageResults.contains(mediaUrl)) {
97 0 : ImageData imageData = imageResults.value(mediaUrl);
98 0 : QString cachedPath = "/images/" + QImageCache::saveImage(mediaUrl, imageData);
99 0 : int w = imageData.image.width();
100 0 : int h = imageData.image.height();
101 0 : if (w > 0 && h > 0) {
102 0 : cachedPath += "?w=" + QString::number(w) + "&h=" + QString::number(h);
103 : }
104 0 : news->mediaImageURL = cachedPath;
105 0 : } else {
106 0 : news->mediaImageURL = "";
107 : }
108 0 : }
109 : }
110 32 : }
|