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() ) );
170void 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 );
206void 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 );
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 and ...
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.
bool exists(const QString &dynamicKeyPart=QString()) const
Returns true if the settings is contained in the underlying QSettings.
T valueWithDefaultOverride(const T &defaultValueOverride, const QString &dynamicKeyPart=QString()) const
Returns the settings value with a defaultValueOverride and with an optional dynamicKeyPart.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(T value, const QString &dynamicKeyPart=QString()) const
Set settings value.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
This class is a composition of two QSettings instances:
QStringList childGroups() const
Returns a list of all key top-level groups that contain keys that can be read using the QSettings obj...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
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.
@ Silent
Don't show task updates (such as completion/failure messages) as operating-system level notifications...
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)