30 , mBaseUrl( feedUrl.toString() )
33 , mSettingsKey( keyForFeed( mBaseUrl ) )
38 QUrlQuery query( feedUrl );
45 if ( feedLanguage.isEmpty() )
47 feedLanguage =
QgsSettings().
value( QStringLiteral(
"locale/userLocale" ), QStringLiteral(
"en_US" ) ).toString().left( 2 );
49 if ( !feedLanguage.isEmpty() && feedLanguage != QStringLiteral(
"C" ) )
50 query.addQueryItem( QStringLiteral(
"lang" ), feedLanguage );
56 if ( latOk && longOk )
59 if ( feedUrl.isLocalFile() )
61 query.addQueryItem( QStringLiteral(
"lat" ), QString::number( static_cast< int >( feedLat ) ) );
62 query.addQueryItem( QStringLiteral(
"lon" ), QString::number( static_cast< int >( feedLong ) ) );
72 if ( feedUrl.isLocalFile() )
74 if ( !query.toString().isEmpty() )
75 mFeedUrl = QUrl( mFeedUrl.toString() +
'_' + query.toString() );
79 mFeedUrl.setQuery( query );
91 const int beforeSize = mEntries.size();
92 mEntries.erase( std::remove_if( mEntries.begin(), mEntries.end(),
93 [key, &dismissed](
const Entry & entry )
95 if ( entry.key == key )
101 } ), mEntries.end() );
102 if ( beforeSize == mEntries.size() )
108 if ( !dismissed.
imageUrl.isEmpty() )
111 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( key );
112 if ( QFile::exists( imagePath ) )
114 QFile::remove( imagePath );
118 if ( !mBlockSignals )
124 const QList< QgsNewsFeedParser::Entry >
entries = mEntries;
125 for (
const Entry &entry : entries )
138 QNetworkRequest req( mFeedUrl );
141 mFetchStartTime = QDateTime::currentDateTimeUtc().toTime_t();
147 QNetworkReply *reply = task->
reply();
154 if ( reply->error() != QNetworkReply::NoError )
161 QMetaObject::invokeMethod(
this,
"onFetch", Qt::QueuedConnection, Q_ARG( QString, task->
contentAsString() ) );
167 void QgsNewsFeedParser::onFetch(
const QString &content )
173 const QVariantList
entries = json.toList();
174 QList< QgsNewsFeedParser::Entry > newEntries;
175 newEntries.reserve( entries.size() );
176 for (
const QVariant &e : entries )
179 const QVariantMap entryMap = e.toMap();
180 newEntry.
key = entryMap.value( QStringLiteral(
"pk" ) ).toInt();
181 newEntry.
title = entryMap.value( QStringLiteral(
"title" ) ).toString();
182 newEntry.
imageUrl = entryMap.value( QStringLiteral(
"image" ) ).toString();
183 newEntry.
content = entryMap.value( QStringLiteral(
"content" ) ).toString();
184 newEntry.
link = entryMap.value( QStringLiteral(
"url" ) ).toString();
185 newEntry.
sticky = entryMap.value( QStringLiteral(
"sticky" ) ).toBool();
187 const uint expiry = entryMap.value( QStringLiteral(
"publish_to" ) ).toUInt( &ok );
189 newEntry.
expiry.setTime_t( expiry );
190 newEntries.append( newEntry );
193 fetchImageForEntry( newEntry );
195 mEntries.append( newEntry );
196 storeEntryInSettings( newEntry );
203 void QgsNewsFeedParser::readStoredEntries()
209 std::sort( existing.begin(), existing.end(), [](
const QString & a,
const QString & b )
211 return a.toInt() < b.toInt();
213 mEntries.reserve( existing.size() );
214 for (
const QString &entry : existing )
216 const Entry e = readEntryFromSettings( entry.toInt() );
217 if ( !e.
expiry.isValid() || e.
expiry > QDateTime::currentDateTime() )
218 mEntries.append( e );
222 mBlockSignals =
true;
224 mBlockSignals =
false;
231 const QString baseSettingsKey = QStringLiteral(
"%1/%2" ).arg( mSettingsKey ).arg( key );
236 entry.
title = settings.
value( QStringLiteral(
"title" ) ).toString();
237 entry.
imageUrl = settings.
value( QStringLiteral(
"imageUrl" ) ).toString();
238 entry.
content = settings.
value( QStringLiteral(
"content" ) ).toString();
239 entry.
link = settings.
value( QStringLiteral(
"link" ) ).toString();
240 entry.
sticky = settings.
value( QStringLiteral(
"sticky" ) ).toBool();
241 entry.
expiry = settings.
value( QStringLiteral(
"expiry" ) ).toDateTime();
245 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( entry.
key );
246 if ( QFile::exists( imagePath ) )
248 const QImage img( imagePath );
249 entry.
image = QPixmap::fromImage( img );
253 fetchImageForEntry( entry );
261 const QString baseSettingsKey = QStringLiteral(
"%1/%2" ).arg( mSettingsKey ).arg( entry.
key );
268 if ( entry.
expiry.isValid() )
278 auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry](
const QgsNewsFeedParser::Entry & candidate )
280 return candidate.key == entry.
key;
282 if ( findIter != mEntries.end() )
284 const int entryIndex =
static_cast< int >( std::distance( mEntries.begin(), findIter ) );
286 QImage img = QImage::fromData( fetcher->
reply()->readAll() );
288 QSize size = img.size();
290 if ( size.width() > 250 )
292 size.setHeight( static_cast< int >( size.height() *
static_cast< double >( 250 ) / size.width() ) );
293 size.setWidth( 250 );
296 if ( size.height() > 177 )
298 size.setWidth( static_cast< int >( size.width() *
static_cast< double >( 177 ) / size.height() ) );
299 size.setHeight( 177 );
303 img = img.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
306 QImage previewImage( size, QImage::Format_ARGB32 );
307 previewImage.fill( Qt::transparent );
308 QPainter previewPainter( &previewImage );
309 previewPainter.setRenderHint( QPainter::Antialiasing,
true );
310 previewPainter.setRenderHint( QPainter::SmoothPixmapTransform,
true );
311 previewPainter.setPen( Qt::NoPen );
312 previewPainter.setBrush( Qt::black );
313 previewPainter.drawRoundedRect( 0, 0, size.width(), size.height(), 8, 8 );
314 previewPainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
315 previewPainter.drawImage( 0, 0, img );
316 previewPainter.end();
320 QDir().mkdir( previewDir );
321 const QString imagePath = QStringLiteral(
"%1/%2.png" ).arg( previewDir ).arg( entry.
key );
322 previewImage.save( imagePath );
324 mEntries[ entryIndex ].image = QPixmap::fromImage( previewImage );
327 fetcher->deleteLater();
334 static QRegularExpression sRegexp( QStringLiteral(
"[^a-zA-Z0-9]" ) );
335 QString res = baseUrl;
336 res = res.replace( sRegexp, QString() );
337 return QStringLiteral(
"NewsFeed/%1" ).arg( res );
#define QgsSetRequestInitiatorClass(request, _class)
QStringList childGroups() const
Returns a list of all key top-level groups that contain keys that can be read using the QSettings obj...
void setDescription(const QString &description)
Sets the task's description.
QgsNewsFeedParser(const QUrl &feedUrl, const QString &authcfg=QString(), QObject *parent=nullptr)
Constructor for QgsNewsFeedParser, parsing the specified feedUrl.
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
void entryDismissed(const QgsNewsFeedParser::Entry &entry)
Emitted whenever an entry is dismissed (as a result of a call to dismissEntry()). ...
Represents a single entry from a news feed.
This class is a composition of two QSettings instances:
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
QNetworkReply * reply()
Returns a reference to the network reply.
QNetworkReply * reply()
Returns the network reply.
QDateTime expiry
Optional auto-expiry time for entry.
Handles HTTP network content fetching in a background task.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling...
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 fetch()
Fetches new entries from the feed's URL.
HTTP network content fetcher.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void finished()
Emitted when content has loaded.
void fetched(const QList< QgsNewsFeedParser::Entry > &entries)
Emitted when entries have fetched from the feed.
bool sticky
true if entry is "sticky" and should always be shown at the top
QString content
HTML content of news entry.
QString title
Entry title.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
QString imageUrl
Optional URL for image associated with entry.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
QString contentAsString() const
Returns the fetched content as a string.
int key
Unique entry identifier.
static QString keyForFeed(const QString &baseUrl)
Returns the settings key used for a feed with the given baseUrl.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
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 QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned...
QUrl link
Optional URL link for entry.
void dismissEntry(int key)
Dismisses an entry with matching key.
QList< QgsNewsFeedParser::Entry > entries() const
Returns a list of existing entries in the feed.
QString authcfg() const
Returns the authentication configuration for the parser.
void dismissAll()
Dismisses all current news items.
QPixmap image
Optional image data.