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 );