30#include <QRegularExpression> 
   50  , mBaseUrl( feedUrl.toString() )
 
   53  , mFeedKey( keyForFeed( mBaseUrl ) )
 
   58  QUrlQuery query( feedUrl );
 
   65  if ( feedLanguage.isEmpty() )
 
   69  if ( !feedLanguage.isEmpty() && feedLanguage != QLatin1String( 
"C" ) )
 
   70    query.addQueryItem( QStringLiteral( 
"lang" ), feedLanguage.mid( 0, 2 ) );
 
   78    if ( feedUrl.isLocalFile() )
 
   80      query.addQueryItem( QStringLiteral( 
"lat" ), QString::number( 
static_cast< int >( feedLat ) ) );
 
   81      query.addQueryItem( QStringLiteral( 
"lon" ), QString::number( 
static_cast< int >( feedLong ) ) );
 
   91  if ( feedUrl.isLocalFile() )
 
   93    if ( !query.toString().isEmpty() )
 
   94      mFeedUrl = QUrl( mFeedUrl.toString() + 
'_' + query.toString() );
 
   98    mFeedUrl.setQuery( query ); 
 
 
  110  const int beforeSize = mEntries.size();
 
  111  mEntries.erase( std::remove_if( mEntries.begin(), mEntries.end(),
 
  112                                  [key, &dismissed]( 
const Entry & entry )
 
  114    if ( entry.key == key )
 
  120  } ), mEntries.end() );
 
  121  if ( beforeSize == mEntries.size() )
 
  124  sTreeNewsFeedEntries->deleteItem( QString::number( key ), {mFeedKey} );
 
  127  if ( !dismissed.imageUrl.isEmpty() )
 
  130    const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( key );
 
  131    if ( QFile::exists( imagePath ) )
 
  133      QFile::remove( imagePath );
 
  137  if ( !mBlockSignals )
 
  138    emit entryDismissed( dismissed );
 
 
  143  const QList< QgsNewsFeedParser::Entry > 
entries = mEntries;
 
 
  157  QNetworkRequest req( mFeedUrl );
 
  160  mFetchStartTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
 
  167    QNetworkReply *reply = task->
reply();
 
  174    if ( reply->error() != QNetworkReply::NoError )
 
  176      QgsMessageLog::logMessage( tr( 
"News feed request failed [error: %1]" ).arg( reply->errorString() ) );
 
  181    QMetaObject::invokeMethod( 
this, 
"onFetch", Qt::QueuedConnection, Q_ARG( QString, task->
contentAsString() ) );
 
 
  187void QgsNewsFeedParser::onFetch( 
const QString &content )
 
  193  const QVariantList 
entries = json.toList();
 
  194  QList< QgsNewsFeedParser::Entry > fetchedEntries;
 
  195  fetchedEntries.reserve( 
entries.size() );
 
  196  for ( 
const QVariant &e : 
entries )
 
  199    const QVariantMap entryMap = e.toMap();
 
  200    incomingEntry.key = entryMap.value( QStringLiteral( 
"pk" ) ).toInt();
 
  201    incomingEntry.title = entryMap.value( QStringLiteral( 
"title" ) ).toString();
 
  202    incomingEntry.imageUrl = entryMap.value( QStringLiteral( 
"image" ) ).toString();
 
  203    incomingEntry.content = entryMap.value( QStringLiteral( 
"content" ) ).toString();
 
  204    incomingEntry.link = entryMap.value( QStringLiteral( 
"url" ) ).toString();
 
  205    incomingEntry.sticky = entryMap.value( QStringLiteral( 
"sticky" ) ).toBool();
 
  206    bool hasExpiry = 
false;
 
  207    const qlonglong expiry = entryMap.value( QStringLiteral( 
"publish_to" ) ).toLongLong( &hasExpiry );
 
  209      incomingEntry.expiry.setSecsSinceEpoch( expiry );
 
  211    fetchedEntries.append( incomingEntry );
 
  214    const auto entryIter { std::find_if( mEntries.begin(), mEntries.end(), [incomingEntry]( 
const QgsNewsFeedParser::Entry & candidate )
 
  216      return candidate.key == incomingEntry.key;
 
  218    const bool entryExists { entryIter != mEntries.end() };
 
  221    if ( hasExpiry && expiry < mFetchStartTime )
 
  226    else if ( entryExists )
 
  228      const bool imageNeedsUpdate = ( entryIter->imageUrl != incomingEntry.imageUrl );
 
  230      if ( imageNeedsUpdate && ! entryIter->imageUrl.isEmpty() )
 
  233        const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entryIter->key );
 
  234        if ( QFile::exists( imagePath ) )
 
  236          QFile::remove( imagePath );
 
  239      *entryIter = incomingEntry;
 
  240      if ( imageNeedsUpdate && ! incomingEntry.imageUrl.isEmpty() )
 
  241        fetchImageForEntry( incomingEntry );
 
  244      storeEntryInSettings( incomingEntry );
 
  248    else if ( !hasExpiry || expiry >= mFetchStartTime )
 
  250      if ( !incomingEntry.imageUrl.isEmpty() )
 
  251        fetchImageForEntry( incomingEntry );
 
  253      mEntries.append( incomingEntry );
 
  254      storeEntryInSettings( incomingEntry );
 
  260  emit 
fetched( fetchedEntries );
 
  263void QgsNewsFeedParser::readStoredEntries()
 
  266  std::sort( existing.begin(), existing.end(), []( 
const QString & a, 
const QString & b )
 
  268    return a.toInt() < b.toInt();
 
  270  mEntries.reserve( existing.size() );
 
  271  for ( 
const QString &entry : existing )
 
  273    const Entry e = readEntryFromSettings( entry.toInt() );
 
  274    if ( !e.expiry.isValid() || e.expiry > QDateTime::currentDateTime() )
 
  275      mEntries.append( e );
 
  279      mBlockSignals = 
true;
 
  281      mBlockSignals = 
false;
 
  296  if ( !entry.imageUrl.isEmpty() )
 
  299    const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entry.key );
 
  300    if ( QFile::exists( imagePath ) )
 
  302      const QImage img( imagePath );
 
  303      entry.image = QPixmap::fromImage( img );
 
  307      fetchImageForEntry( entry );
 
  320  if ( entry.
expiry.isValid() )
 
  330    const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry]( 
const QgsNewsFeedParser::Entry & candidate )
 
  332      return candidate.key == entry.key;
 
  334    if ( findIter != mEntries.end() )
 
  336      const int entryIndex = 
static_cast< int >( std::distance( mEntries.begin(), findIter ) );
 
  338      QImage img = QImage::fromData( fetcher->
reply()->readAll() );
 
  340      QSize size = img.size();
 
  342      if ( size.width() > 250 )
 
  344        size.setHeight( 
static_cast< int >( size.height() * 
static_cast< double >( 250 ) / size.width() ) );
 
  345        size.setWidth( 250 );
 
  348      if ( size.height() > 177 )
 
  350        size.setWidth( 
static_cast< int >( size.width() * 
static_cast< double >( 177 ) / size.height() ) );
 
  351        size.setHeight( 177 );
 
  355        img = img.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
 
  358      QImage previewImage( size, QImage::Format_ARGB32 );
 
  359      previewImage.fill( Qt::transparent );
 
  360      QPainter previewPainter( &previewImage );
 
  361      previewPainter.setRenderHint( QPainter::Antialiasing, 
true );
 
  362      previewPainter.setRenderHint( QPainter::SmoothPixmapTransform, 
true );
 
  363      previewPainter.setPen( Qt::NoPen );
 
  364      previewPainter.setBrush( Qt::black );
 
  365      previewPainter.drawRoundedRect( 0, 0, size.width(), size.height(), 8, 8 );
 
  366      previewPainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
 
  367      previewPainter.drawImage( 0, 0, img );
 
  368      previewPainter.end();
 
  372      QDir().mkdir( previewDir );
 
  373      const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entry.
key );
 
  374      previewImage.save( imagePath );
 
  376      mEntries[ entryIndex ].image = QPixmap::fromImage( previewImage );
 
  379    fetcher->deleteLater();
 
  386  static const QRegularExpression sRegexp( QStringLiteral( 
"[^a-zA-Z0-9]" ) );
 
  387  QString res = baseUrl;
 
  388  res = res.replace( sRegexp, QString() );
 
 
QFlags< SettingsOption > SettingsOptions
 
static const QgsSettingsEntryString * settingsLocaleUserLocale
Settings entry locale user locale.
 
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 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 QgsSettingsTreeNamedListNode * sTreeNewsFeedEntries
 
static const QgsSettingsEntryString * settingsFeedEntryTitle
 
void dismissEntry(int key)
Dismisses an entry with matching key.
 
static const QgsSettingsEntryString * settingsFeedEntryLink
 
void fetch()
Fetches new entries from the feed's URL.
 
void fetched(const QList< QgsNewsFeedParser::Entry > &entries)
Emitted when entries have been fetched from the feed.
 
static const QgsSettingsEntryString * settingsFeedEntryImageUrl
 
static const QgsSettingsEntryDouble * settingsFeedLatitude
 
QString authcfg() const
Returns the authentication configuration for the parser.
 
static const QgsSettingsEntryInteger64 * settingsFeedLastFetchTime
 
QgsNewsFeedParser(const QUrl &feedUrl, const QString &authcfg=QString(), QObject *parent=nullptr)
Constructor for QgsNewsFeedParser, parsing the specified feedUrl.
 
static const QgsSettingsEntryBool * settingsFeedEntrySticky
 
void dismissAll()
Dismisses all current news items.
 
static const QgsSettingsEntryDouble * settingsFeedLongitude
 
static const QgsSettingsEntryString * settingsFeedLanguage
 
static const QgsSettingsEntryString * settingsFeedEntryContent
 
void entryUpdated(const QgsNewsFeedParser::Entry &entry)
Emitted whenever an existing entry is available from the feed (as a result of a call to fetch()).
 
static const QgsSettingsEntryVariant * settingsFeedEntryExpiry
 
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...
 
QList< QgsNewsFeedParser::Entry > entries() const
Returns a list of existing entries in the feed.
 
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(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
 
bool exists(const QString &dynamicKeyPart=QString()) const
Returns true if the settings is contained in the underlying QSettings.
 
A boolean settings entry.
 
A 64 bits integer (long long) settings entry.
 
A variant settings entry.
 
void deleteItem(const QString &item, const QStringList &parentsNamedItems=QStringList())
Deletes a named item from the named list node.
 
QStringList items(const QStringList &parentsNamedItems=QStringList()) const
Returns the list of items.
 
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)