21 #include "qgssettings.h" 
   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() )
 
  107   QgsSettings().remove( QStringLiteral( 
"%1/%2" ).arg( mSettingsKey ).arg( key ), QgsSettings::Core );
 
  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()
 
  208   QgsSettings settings;
 
  210   settings.beginGroup( mSettingsKey, QgsSettings::Core );
 
  211   QStringList existing = settings.childGroups();
 
  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 );
 
  235   QgsSettings settings;
 
  236   settings.beginGroup( baseSettingsKey, QgsSettings::Core );
 
  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 );
 
  265   QgsSettings settings;
 
  266   settings.setValue( QStringLiteral( 
"%1/title" ).arg( baseSettingsKey ), entry.
title, QgsSettings::Core );
 
  267   settings.setValue( QStringLiteral( 
"%1/imageUrl" ).arg( baseSettingsKey ), entry.
imageUrl, QgsSettings::Core );
 
  268   settings.setValue( QStringLiteral( 
"%1/content" ).arg( baseSettingsKey ), entry.
content, QgsSettings::Core );
 
  269   settings.setValue( QStringLiteral( 
"%1/link" ).arg( baseSettingsKey ), entry.
link, QgsSettings::Core );
 
  270   settings.setValue( QStringLiteral( 
"%1/sticky" ).arg( baseSettingsKey ), entry.
sticky, QgsSettings::Core );
 
  271   if ( entry.
expiry.isValid() )
 
  272     settings.setValue( QStringLiteral( 
"%1/expiry" ).arg( baseSettingsKey ), entry.
expiry, QgsSettings::Core );
 
  281     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 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.
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)