16#include "moc_qgsnewsfeedparser.cpp" 
   31#include <QRegularExpression> 
   51  , mBaseUrl( feedUrl.toString() )
 
   54  , mFeedKey( keyForFeed( mBaseUrl ) )
 
   59  QUrlQuery query( feedUrl );
 
   66  if ( feedLanguage.isEmpty() )
 
   70  if ( !feedLanguage.isEmpty() && feedLanguage != QLatin1String( 
"C" ) )
 
   71    query.addQueryItem( QStringLiteral( 
"lang" ), feedLanguage.mid( 0, 2 ) );
 
   79    if ( feedUrl.isLocalFile() )
 
   81      query.addQueryItem( QStringLiteral( 
"lat" ), QString::number( 
static_cast< int >( feedLat ) ) );
 
   82      query.addQueryItem( QStringLiteral( 
"lon" ), QString::number( 
static_cast< int >( feedLong ) ) );
 
   92  if ( feedUrl.isLocalFile() )
 
   94    if ( !query.toString().isEmpty() )
 
   95      mFeedUrl = QUrl( mFeedUrl.toString() + 
'_' + query.toString() );
 
   99    mFeedUrl.setQuery( query ); 
 
 
  111  const int beforeSize = mEntries.size();
 
  112  mEntries.erase( std::remove_if( mEntries.begin(), mEntries.end(),
 
  113                                  [key, &dismissed]( 
const Entry & entry )
 
  115    if ( entry.key == key )
 
  121  } ), mEntries.end() );
 
  122  if ( beforeSize == mEntries.size() )
 
  125  sTreeNewsFeedEntries->deleteItem( QString::number( key ), {mFeedKey} );
 
  128  if ( !dismissed.imageUrl.isEmpty() )
 
  131    const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( key );
 
  132    if ( QFile::exists( imagePath ) )
 
  134      QFile::remove( imagePath );
 
  138  if ( !mBlockSignals )
 
  139    emit entryDismissed( dismissed );
 
 
  144  const QList< QgsNewsFeedParser::Entry > 
entries = mEntries;
 
 
  158  QNetworkRequest req( mFeedUrl );
 
  161  mFetchStartTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
 
  168    QNetworkReply *reply = task->
reply();
 
  175    if ( reply->error() != QNetworkReply::NoError )
 
  177      QgsMessageLog::logMessage( tr( 
"News feed request failed [error: %1]" ).arg( reply->errorString() ) );
 
  182    QMetaObject::invokeMethod( 
this, 
"onFetch", Qt::QueuedConnection, Q_ARG( QString, task->
contentAsString() ) );
 
 
  188void QgsNewsFeedParser::onFetch( 
const QString &content )
 
  194  const QVariantList 
entries = json.toList();
 
  195  QList< QgsNewsFeedParser::Entry > fetchedEntries;
 
  196  fetchedEntries.reserve( 
entries.size() );
 
  197  for ( 
const QVariant &e : 
entries )
 
  200    const QVariantMap entryMap = e.toMap();
 
  201    incomingEntry.key = entryMap.value( QStringLiteral( 
"pk" ) ).toInt();
 
  202    incomingEntry.title = entryMap.value( QStringLiteral( 
"title" ) ).toString();
 
  203    incomingEntry.imageUrl = entryMap.value( QStringLiteral( 
"image" ) ).toString();
 
  204    incomingEntry.content = entryMap.value( QStringLiteral( 
"content" ) ).toString();
 
  205    incomingEntry.link = entryMap.value( QStringLiteral( 
"url" ) ).toString();
 
  206    incomingEntry.sticky = entryMap.value( QStringLiteral( 
"sticky" ) ).toBool();
 
  207    bool hasExpiry = 
false;
 
  208    const qlonglong expiry = entryMap.value( QStringLiteral( 
"publish_to" ) ).toLongLong( &hasExpiry );
 
  210      incomingEntry.expiry.setSecsSinceEpoch( expiry );
 
  212    fetchedEntries.append( incomingEntry );
 
  215    const auto entryIter { std::find_if( mEntries.begin(), mEntries.end(), [incomingEntry]( 
const QgsNewsFeedParser::Entry & candidate )
 
  217      return candidate.key == incomingEntry.key;
 
  219    const bool entryExists { entryIter != mEntries.end() };
 
  222    if ( hasExpiry && expiry < mFetchStartTime )
 
  227    else if ( entryExists )
 
  229      const bool imageNeedsUpdate = ( entryIter->imageUrl != incomingEntry.imageUrl );
 
  231      if ( imageNeedsUpdate && ! entryIter->imageUrl.isEmpty() )
 
  234        const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entryIter->key );
 
  235        if ( QFile::exists( imagePath ) )
 
  237          QFile::remove( imagePath );
 
  240      *entryIter = incomingEntry;
 
  241      if ( imageNeedsUpdate && ! incomingEntry.imageUrl.isEmpty() )
 
  242        fetchImageForEntry( incomingEntry );
 
  245      storeEntryInSettings( incomingEntry );
 
  249    else if ( !hasExpiry || expiry >= mFetchStartTime )
 
  251      if ( !incomingEntry.imageUrl.isEmpty() )
 
  252        fetchImageForEntry( incomingEntry );
 
  254      mEntries.append( incomingEntry );
 
  255      storeEntryInSettings( incomingEntry );
 
  261  emit 
fetched( fetchedEntries );
 
  264void QgsNewsFeedParser::readStoredEntries()
 
  267  std::sort( existing.begin(), existing.end(), []( 
const QString & a, 
const QString & b )
 
  269    return a.toInt() < b.toInt();
 
  271  mEntries.reserve( existing.size() );
 
  272  for ( 
const QString &entry : existing )
 
  274    const Entry e = readEntryFromSettings( entry.toInt() );
 
  275    if ( !e.expiry.isValid() || e.expiry > QDateTime::currentDateTime() )
 
  276      mEntries.append( e );
 
  280      mBlockSignals = 
true;
 
  282      mBlockSignals = 
false;
 
  297  if ( !entry.imageUrl.isEmpty() )
 
  300    const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entry.key );
 
  301    if ( QFile::exists( imagePath ) )
 
  303      const QImage img( imagePath );
 
  304      entry.image = QPixmap::fromImage( img );
 
  308      fetchImageForEntry( entry );
 
  321  if ( entry.
expiry.isValid() )
 
  331    const auto findIter = std::find_if( mEntries.begin(), mEntries.end(), [entry]( 
const QgsNewsFeedParser::Entry & candidate )
 
  333      return candidate.key == entry.key;
 
  335    if ( findIter != mEntries.end() )
 
  337      const int entryIndex = 
static_cast< int >( std::distance( mEntries.begin(), findIter ) );
 
  339      QImage img = QImage::fromData( fetcher->
reply()->readAll() );
 
  341      QSize size = img.size();
 
  343      if ( size.width() > 250 )
 
  345        size.setHeight( 
static_cast< int >( size.height() * 
static_cast< double >( 250 ) / size.width() ) );
 
  346        size.setWidth( 250 );
 
  349      if ( size.height() > 177 )
 
  351        size.setWidth( 
static_cast< int >( size.width() * 
static_cast< double >( 177 ) / size.height() ) );
 
  352        size.setHeight( 177 );
 
  356        img = img.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
 
  359      QImage previewImage( size, QImage::Format_ARGB32 );
 
  360      previewImage.fill( Qt::transparent );
 
  361      QPainter previewPainter( &previewImage );
 
  362      previewPainter.setRenderHint( QPainter::Antialiasing, 
true );
 
  363      previewPainter.setRenderHint( QPainter::SmoothPixmapTransform, 
true );
 
  364      previewPainter.setPen( Qt::NoPen );
 
  365      previewPainter.setBrush( Qt::black );
 
  366      previewPainter.drawRoundedRect( 0, 0, size.width(), size.height(), 8, 8 );
 
  367      previewPainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
 
  368      previewPainter.drawImage( 0, 0, img );
 
  369      previewPainter.end();
 
  373      QDir().mkdir( previewDir );
 
  374      const QString imagePath = QStringLiteral( 
"%1/%2.png" ).arg( previewDir ).arg( entry.
key );
 
  375      previewImage.save( imagePath );
 
  377      mEntries[ entryIndex ].image = QPixmap::fromImage( previewImage );
 
  380    fetcher->deleteLater();
 
  387  static const QRegularExpression sRegexp( QStringLiteral( 
"[^a-zA-Z0-9]" ) );
 
  388  QString res = baseUrl;
 
  389  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)