21 #include "qgssettings.h"
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() )
107 QgsSettings().remove( QStringLiteral(
"%1/%2" ).arg( mSettingsKey ).arg( key ), QgsSettings::Core );
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()
208 QgsSettings settings;
210 settings.beginGroup( mSettingsKey, QgsSettings::Core );
211 QStringList existing = settings.childGroups();
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 );
235 QgsSettings settings;
236 settings.beginGroup( baseSettingsKey, QgsSettings::Core );
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 );
265 QgsSettings settings;
266 settings.setValue( QStringLiteral(
"%1/title" ).arg( baseSettingsKey ), entry.
title, QgsSettings::Core );
267 settings.setValue( QStringLiteral(
"%1/imageUrl" ).arg( baseSettingsKey ), entry.
imageUrl, QgsSettings::Core );
268 settings.setValue( QStringLiteral(
"%1/content" ).arg( baseSettingsKey ), entry.
content, QgsSettings::Core );
269 settings.setValue( QStringLiteral(
"%1/link" ).arg( baseSettingsKey ), entry.
link, QgsSettings::Core );
270 settings.setValue( QStringLiteral(
"%1/sticky" ).arg( baseSettingsKey ), entry.
sticky, QgsSettings::Core );
271 if ( entry.
expiry.isValid() )
272 settings.setValue( QStringLiteral(
"%1/expiry" ).arg( baseSettingsKey ), entry.
expiry, QgsSettings::Core );
281 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 QRegularExpression sRegexp( QStringLiteral(
"[^a-zA-Z0-9]" ) );
338 QString res = baseUrl;
339 res = res.replace( sRegexp, QString() );
340 return QStringLiteral(
"NewsFeed/%1" ).arg( res );
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
static const QgsSettingsEntryString settingsLocaleUserLocale
Settings entry locale user locale.
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned.
Handles HTTP network content fetching in a background task.
QString contentAsString() const
Returns the fetched content as a string.
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
QNetworkReply * reply()
Returns the network reply.
HTTP network content fetcher.
void finished()
Emitted when content has loaded.
QNetworkReply * reply()
Returns a reference to the network reply.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
Represents a single entry from a news feed.
QString content
HTML content of news entry.
bool sticky
true if entry is "sticky" and should always be shown at the top
QUrl link
Optional URL link for entry.
QString imageUrl
Optional URL for image associated with entry.
QDateTime expiry
Optional auto-expiry time for entry.
int key
Unique entry identifier.
QString title
Entry title.
static const QgsSettingsEntryDouble settingsFeedLongitude
Settings entry feed longitude.
void dismissEntry(int key)
Dismisses an entry with matching key.
void fetch()
Fetches new entries from the feed's URL.
static const QgsSettingsEntryInteger settingsFeedLastFetchTime
Settings entry last fetch time.
static const QgsSettingsEntryDouble settingsFeedLatitude
Settings entry feed latitude.
void fetched(const QList< QgsNewsFeedParser::Entry > &entries)
Emitted when entries have fetched from the feed.
QString authcfg() const
Returns the authentication configuration for the parser.
QgsNewsFeedParser(const QUrl &feedUrl, const QString &authcfg=QString(), QObject *parent=nullptr)
Constructor for QgsNewsFeedParser, parsing the specified feedUrl.
void dismissAll()
Dismisses all current news items.
static QString keyForFeed(const QString &baseUrl)
Returns the settings key used for a feed with the given baseUrl.
void entryAdded(const QgsNewsFeedParser::Entry &entry)
Emitted whenever a new entry is available from the feed (as a result of a call to fetch()).
void imageFetched(int key, const QPixmap &pixmap)
Emitted when the image attached to the entry with the specified key has been fetched and is now avail...
static const QgsSettingsEntryString settingsFeedLanguage
Settings entry feed language.
QList< QgsNewsFeedParser::Entry > entries() const
Returns a list of existing entries in the feed.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
@ CanCancel
Task can be canceled.
@ CancelWithoutPrompt
Task can be canceled without any users prompts, e.g. when closing a project or QGIS.
void setDescription(const QString &description)
Sets the task's description.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
#define QgsSetRequestInitiatorClass(request, _class)