29 #include <QRegularExpression>
33 , mBaseUrl( feedUrl.toString() )
36 , mSettingsKey( keyForFeed( mBaseUrl ) )
41 QUrlQuery query( feedUrl );
48 if ( feedLanguage.isEmpty() )
52 if ( !feedLanguage.isEmpty() && feedLanguage != QLatin1String(
"C" ) )
53 query.addQueryItem( QStringLiteral(
"lang" ), feedLanguage.mid( 0, 2 ) );
61 if ( feedUrl.isLocalFile() )
63 query.addQueryItem( QStringLiteral(
"lat" ), QString::number(
static_cast< int >( feedLat ) ) );
64 query.addQueryItem( QStringLiteral(
"lon" ), QString::number(
static_cast< int >( feedLong ) ) );
74 if ( feedUrl.isLocalFile() )
76 if ( !query.toString().isEmpty() )
77 mFeedUrl = QUrl( mFeedUrl.toString() +
'_' + query.toString() );
81 mFeedUrl.setQuery( query );
93 const int beforeSize = mEntries.size();
94 mEntries.erase( std::remove_if( mEntries.begin(), mEntries.end(),
95 [key, &dismissed](
const Entry & entry )
97 if ( entry.key == key )
103 } ), mEntries.end() );
104 if ( beforeSize == mEntries.size() )
110 if ( !dismissed.imageUrl.isEmpty() )
113 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( key );
114 if ( QFile::exists( imagePath ) )
116 QFile::remove( imagePath );
120 if ( !mBlockSignals )
121 emit entryDismissed( dismissed );
126 const QList< QgsNewsFeedParser::Entry >
entries = mEntries;
140 QNetworkRequest req( mFeedUrl );
143 mFetchStartTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
150 QNetworkReply *reply = task->
reply();
157 if ( reply->error() != QNetworkReply::NoError )
159 QgsMessageLog::logMessage( tr(
"News feed request failed [error: %1]" ).arg( reply->errorString() ) );
164 QMetaObject::invokeMethod(
this,
"onFetch", Qt::QueuedConnection, Q_ARG( QString, task->
contentAsString() ) );
170 void QgsNewsFeedParser::onFetch(
const QString &content )
176 const QVariantList
entries = json.toList();
177 QList< QgsNewsFeedParser::Entry > newEntries;
178 newEntries.reserve(
entries.size() );
179 for (
const QVariant &e :
entries )
182 const QVariantMap entryMap = e.toMap();
183 newEntry.key = entryMap.value( QStringLiteral(
"pk" ) ).toInt();
184 newEntry.title = entryMap.value( QStringLiteral(
"title" ) ).toString();
185 newEntry.imageUrl = entryMap.value( QStringLiteral(
"image" ) ).toString();
186 newEntry.content = entryMap.value( QStringLiteral(
"content" ) ).toString();
187 newEntry.link = entryMap.value( QStringLiteral(
"url" ) ).toString();
188 newEntry.sticky = entryMap.value( QStringLiteral(
"sticky" ) ).toBool();
190 const uint expiry = entryMap.value( QStringLiteral(
"publish_to" ) ).toUInt( &ok );
192 newEntry.expiry.setSecsSinceEpoch( expiry );
193 newEntries.append( newEntry );
195 if ( !newEntry.imageUrl.isEmpty() )
196 fetchImageForEntry( newEntry );
198 mEntries.append( newEntry );
199 storeEntryInSettings( newEntry );
206 void QgsNewsFeedParser::readStoredEntries()
212 std::sort( existing.begin(), existing.end(), [](
const QString & a,
const QString & b )
214 return a.toInt() < b.toInt();
216 mEntries.reserve( existing.size() );
217 for (
const QString &entry : existing )
219 const Entry e = readEntryFromSettings( entry.toInt() );
220 if ( !e.expiry.isValid() || e.expiry > QDateTime::currentDateTime() )
221 mEntries.append( e );
225 mBlockSignals =
true;
227 mBlockSignals =
false;
234 const QString baseSettingsKey = QStringLiteral(
"%1/%2" ).arg( mSettingsKey ).arg( key );
239 entry.title = settings.
value( QStringLiteral(
"title" ) ).toString();
240 entry.imageUrl = settings.
value( QStringLiteral(
"imageUrl" ) ).toString();
241 entry.content = settings.
value( QStringLiteral(
"content" ) ).toString();
242 entry.link = settings.
value( QStringLiteral(
"link" ) ).toString();
243 entry.sticky = settings.
value( QStringLiteral(
"sticky" ) ).toBool();
244 entry.expiry = settings.
value( QStringLiteral(
"expiry" ) ).toDateTime();
245 if ( !entry.imageUrl.isEmpty() )
248 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( entry.key );
249 if ( QFile::exists( imagePath ) )
251 const QImage img( imagePath );
252 entry.image = QPixmap::fromImage( img );
256 fetchImageForEntry( entry );
264 const QString baseSettingsKey = QStringLiteral(
"%1/%2" ).arg( mSettingsKey ).arg( entry.
key );
271 if ( entry.
expiry.isValid() )
281 const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry](
const QgsNewsFeedParser::Entry & candidate )
283 return candidate.key == entry.key;
285 if ( findIter != mEntries.end() )
287 const int entryIndex =
static_cast< int >( std::distance( mEntries.begin(), findIter ) );
289 QImage img = QImage::fromData( fetcher->
reply()->readAll() );
291 QSize size = img.size();
293 if ( size.width() > 250 )
295 size.setHeight(
static_cast< int >( size.height() *
static_cast< double >( 250 ) / size.width() ) );
296 size.setWidth( 250 );
299 if ( size.height() > 177 )
301 size.setWidth(
static_cast< int >( size.width() *
static_cast< double >( 177 ) / size.height() ) );
302 size.setHeight( 177 );
306 img = img.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
309 QImage previewImage( size, QImage::Format_ARGB32 );
310 previewImage.fill( Qt::transparent );
311 QPainter previewPainter( &previewImage );
312 previewPainter.setRenderHint( QPainter::Antialiasing,
true );
313 previewPainter.setRenderHint( QPainter::SmoothPixmapTransform,
true );
314 previewPainter.setPen( Qt::NoPen );
315 previewPainter.setBrush( Qt::black );
316 previewPainter.drawRoundedRect( 0, 0, size.width(), size.height(), 8, 8 );
317 previewPainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
318 previewPainter.drawImage( 0, 0, img );
319 previewPainter.end();
323 QDir().mkdir( previewDir );
324 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( entry.
key );
325 previewImage.save( imagePath );
327 mEntries[ entryIndex ].image = QPixmap::fromImage( previewImage );
330 fetcher->deleteLater();
337 static const QRegularExpression sRegexp( QStringLiteral(
"[^a-zA-Z0-9]" ) );
338 QString res = baseUrl;
339 res = res.replace( sRegexp, QString() );
340 return QStringLiteral(
"NewsFeed/%1" ).arg( res );