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() ) );
 
  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()
 
  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.
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.
double value(const QString &dynamicKeyPart=QString(), bool useDefaultValueOverride=false, double defaultValueOverride=0.0) const
Returns settings value.
bool setValue(qlonglong value, const QString &dynamicKeyPart=QString()) const
Set settings value.
qlonglong value(const QString &dynamicKeyPart=QString(), bool useDefaultValueOverride=false, qlonglong defaultValueOverride=0) const
Returns settings value.
QString value(const QString &dynamicKeyPart=QString(), bool useDefaultValueOverride=false, const QString &defaultValueOverride=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.
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)