2020-02-28 23:25:08 +01:00
/**
2020-08-14 20:56:04 +02:00
* SPDX - FileCopyrightText : 2020 Tobias Fella < fella @ posteo . de >
2021-04-08 13:16:36 +02:00
* SPDX - FileCopyrightText : 2021 Bart De Vries < bart @ mogwai . be >
2020-02-28 23:25:08 +01:00
*
2020-08-14 20:56:04 +02:00
* SPDX - License - Identifier : GPL - 2.0 - only OR GPL - 3.0 - only OR LicenseRef - KDE - Accepted - GPL
2020-02-28 23:25:08 +01:00
*/
2021-03-12 00:19:04 +01:00
# include <QCryptographicHash>
2020-06-06 00:05:32 +02:00
# include <QDateTime>
2021-03-12 00:19:04 +01:00
# include <QDebug>
2021-05-01 21:35:37 +02:00
# include <QDir>
# include <QDomElement>
2020-04-26 23:40:09 +02:00
# include <QFile>
# include <QFileInfo>
2021-05-01 21:35:37 +02:00
# include <QMultiMap>
2020-02-28 23:25:08 +01:00
# include <QNetworkAccessManager>
# include <QNetworkReply>
2020-04-25 22:16:19 +02:00
# include <QStandardPaths>
2020-05-02 12:26:00 +02:00
# include <QTextDocumentFragment>
2020-02-28 23:25:08 +01:00
2020-02-29 15:34:12 +01:00
# include <Syndication/Syndication>
2020-02-28 23:25:08 +01:00
2020-03-16 22:37:04 +01:00
# include "database.h"
2021-06-23 21:11:04 +02:00
# include "enclosure.h"
2020-04-22 02:17:57 +02:00
# include "fetcher.h"
2021-06-05 20:12:42 +02:00
# include "fetcherlogging.h"
2021-04-10 08:46:14 +02:00
# include "settingsmanager.h"
2020-03-16 22:37:04 +01:00
2020-04-22 02:17:57 +02:00
Fetcher : : Fetcher ( )
{
2021-04-20 19:17:46 +02:00
m_updateProgress = - 1 ;
m_updateTotal = - 1 ;
m_updating = false ;
2020-04-25 22:16:19 +02:00
manager = new QNetworkAccessManager ( this ) ;
manager - > setRedirectPolicy ( QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
manager - > setStrictTransportSecurityEnabled ( true ) ;
manager - > enableStrictTransportSecurityStore ( true ) ;
2020-02-28 23:25:08 +01:00
}
2020-11-01 13:18:11 +01:00
void Fetcher : : fetch ( const QString & url )
2020-02-28 23:25:08 +01:00
{
2021-04-26 16:58:22 +02:00
QStringList urls ( url ) ;
fetch ( urls ) ;
2020-02-28 23:25:08 +01:00
}
2020-04-25 22:16:19 +02:00
2021-04-20 20:53:24 +02:00
void Fetcher : : fetch ( const QStringList & urls )
2020-05-31 18:17:25 +02:00
{
2021-05-01 21:35:37 +02:00
if ( m_updating )
return ; // update is already running, do nothing
2021-04-20 19:17:46 +02:00
m_updating = true ;
m_updateProgress = 0 ;
2021-04-20 20:53:24 +02:00
m_updateTotal = urls . count ( ) ;
2021-04-20 19:17:46 +02:00
connect ( this , & Fetcher : : updateProgressChanged , this , & Fetcher : : updateMonitor ) ;
Q_EMIT updatingChanged ( m_updating ) ;
Q_EMIT updateProgressChanged ( m_updateProgress ) ;
Q_EMIT updateTotalChanged ( m_updateTotal ) ;
2021-05-01 21:35:37 +02:00
for ( int i = 0 ; i < urls . count ( ) ; i + + ) {
2021-04-26 16:58:22 +02:00
retrieveFeed ( urls [ i ] ) ;
2021-04-20 20:53:24 +02:00
}
}
void Fetcher : : fetchAll ( )
{
QStringList urls ;
QSqlQuery query ;
2020-05-31 18:17:25 +02:00
query . prepare ( QStringLiteral ( " SELECT url FROM Feeds; " ) ) ;
Database : : instance ( ) . execute ( query ) ;
2020-06-06 00:05:32 +02:00
while ( query . next ( ) ) {
2021-05-01 21:35:37 +02:00
urls + = query . value ( 0 ) . toString ( ) ;
;
2020-05-31 18:17:25 +02:00
}
2021-04-20 20:53:24 +02:00
2021-05-01 18:59:08 +00:00
if ( urls . count ( ) > 0 ) {
2021-04-20 20:53:24 +02:00
fetch ( urls ) ;
2021-05-01 18:59:08 +00:00
}
2020-05-31 18:17:25 +02:00
}
2021-04-26 16:58:22 +02:00
void Fetcher : : retrieveFeed ( const QString & url )
{
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Starting to fetch " < < url ;
2021-04-26 16:58:22 +02:00
Q_EMIT startedFetchingFeed ( url ) ;
QNetworkRequest request ( ( QUrl ( url ) ) ) ;
request . setTransferTimeout ( ) ;
QNetworkReply * reply = get ( request ) ;
connect ( reply , & QNetworkReply : : finished , this , [ this , url , reply ] ( ) {
2021-05-01 21:35:37 +02:00
if ( reply - > error ( ) ) {
2021-04-26 16:58:22 +02:00
qWarning ( ) < < " Error fetching feed " ;
qWarning ( ) < < reply - > errorString ( ) ;
2021-05-14 16:46:54 +02:00
Q_EMIT error ( Error : : Type : : FeedUpdate , url , QString ( ) , reply - > error ( ) , reply - > errorString ( ) ) ;
2021-04-26 16:58:22 +02:00
} else {
QByteArray data = reply - > readAll ( ) ;
Syndication : : DocumentSource * document = new Syndication : : DocumentSource ( data , url ) ;
Syndication : : FeedPtr feed = Syndication : : parserCollection ( ) - > parse ( * document , QStringLiteral ( " Atom " ) ) ;
processFeed ( feed , url ) ;
}
m_updateProgress + + ;
Q_EMIT updateProgressChanged ( m_updateProgress ) ;
delete reply ;
} ) ;
}
2021-04-20 19:17:46 +02:00
void Fetcher : : updateMonitor ( int progress )
{
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Update monitor " < < progress < < " / " < < m_updateTotal ;
2021-04-20 19:17:46 +02:00
// this method will watch for the end of the update process
if ( progress > - 1 & & m_updateTotal > - 1 & & progress = = m_updateTotal ) {
m_updating = false ;
m_updateProgress = - 1 ;
m_updateTotal = - 1 ;
disconnect ( this , & Fetcher : : updateProgressChanged , this , & Fetcher : : updateMonitor ) ;
Q_EMIT updatingChanged ( m_updating ) ;
2021-05-01 21:35:37 +02:00
// Q_EMIT updateProgressChanged(m_updateProgress);
// Q_EMIT updateTotalChanged(m_updateTotal);
2021-04-20 19:17:46 +02:00
}
}
2020-11-01 13:18:11 +01:00
void Fetcher : : processFeed ( Syndication : : FeedPtr feed , const QString & url )
2020-05-18 16:47:12 +02:00
{
if ( feed . isNull ( ) )
return ;
2021-04-09 17:03:02 +02:00
// First check if this is a newly added feed
bool isNewFeed = false ;
QSqlQuery query ;
query . prepare ( QStringLiteral ( " SELECT new FROM Feeds WHERE url=:url; " ) ) ;
query . bindValue ( QStringLiteral ( " :url " ) , url ) ;
Database : : instance ( ) . execute ( query ) ;
2021-04-19 16:53:40 +02:00
if ( query . next ( ) ) {
2021-04-09 17:03:02 +02:00
isNewFeed = query . value ( QStringLiteral ( " new " ) ) . toBool ( ) ;
2021-04-19 16:53:40 +02:00
} else {
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Feed not found in database " < < url ;
2021-04-19 16:53:40 +02:00
return ;
}
2021-05-01 21:35:37 +02:00
if ( isNewFeed )
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " New feed " < < feed - > title ( ) < < " : " < < isNewFeed ;
2021-04-09 17:03:02 +02:00
2021-04-05 12:43:35 +02:00
// Retrieve "other" fields; this will include the "itunes" tags
QMultiMap < QString , QDomElement > otherItems = feed - > additionalProperties ( ) ;
2021-05-07 10:26:58 +02:00
query . prepare ( QStringLiteral ( " UPDATE Feeds SET name=:name, image=:image, link=:link, description=:description, lastUpdated=:lastUpdated WHERE url=:url; " ) ) ;
2020-05-18 16:47:12 +02:00
query . bindValue ( QStringLiteral ( " :name " ) , feed - > title ( ) ) ;
query . bindValue ( QStringLiteral ( " :url " ) , url ) ;
2020-05-30 17:33:08 +02:00
query . bindValue ( QStringLiteral ( " :link " ) , feed - > link ( ) ) ;
query . bindValue ( QStringLiteral ( " :description " ) , feed - > description ( ) ) ;
2020-06-06 00:05:32 +02:00
QDateTime current = QDateTime : : currentDateTime ( ) ;
query . bindValue ( QStringLiteral ( " :lastUpdated " ) , current . toSecsSinceEpoch ( ) ) ;
2021-04-05 12:43:35 +02:00
// Process authors
QString authorname , authoremail ;
if ( feed - > authors ( ) . count ( ) > 0 ) {
for ( auto & author : feed - > authors ( ) ) {
processAuthor ( url , QLatin1String ( " " ) , author - > name ( ) , QLatin1String ( " " ) , QLatin1String ( " " ) ) ;
}
} else {
// Try to find itunes fields if plain author doesn't exist
QString authorname , authoremail ;
// First try the "itunes:owner" tag, if that doesn't succeed, then try the "itunes:author" tag
if ( otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdowner " ) ) . hasChildNodes ( ) ) {
QDomNodeList nodelist = otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdowner " ) ) . childNodes ( ) ;
2021-05-01 18:59:08 +00:00
for ( int i = 0 ; i < nodelist . length ( ) ; i + + ) {
2021-04-05 12:43:35 +02:00
if ( nodelist . item ( i ) . nodeName ( ) = = QStringLiteral ( " itunes:name " ) ) {
authorname = nodelist . item ( i ) . toElement ( ) . text ( ) ;
} else if ( nodelist . item ( i ) . nodeName ( ) = = QStringLiteral ( " itunes:email " ) ) {
authoremail = nodelist . item ( i ) . toElement ( ) . text ( ) ;
}
}
} else {
authorname = otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdauthor " ) ) . text ( ) ;
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " authorname " < < authorname ;
2021-04-05 12:43:35 +02:00
}
2021-05-01 18:59:08 +00:00
if ( ! authorname . isEmpty ( ) ) {
processAuthor ( url , QLatin1String ( " " ) , authorname , QLatin1String ( " " ) , authoremail ) ;
}
2020-05-30 17:33:08 +02:00
}
2021-04-05 12:43:35 +02:00
QString image = feed - > image ( ) - > url ( ) ;
// If there is no regular image tag, then try the itunes tags
if ( image . isEmpty ( ) ) {
if ( otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdimage " ) ) . hasAttribute ( QStringLiteral ( " href " ) ) ) {
image = otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdimage " ) ) . attribute ( QStringLiteral ( " href " ) ) ;
}
}
if ( image . startsWith ( QStringLiteral ( " / " ) ) )
image = QUrl ( url ) . adjusted ( QUrl : : RemovePath ) . toString ( ) + image ;
2020-05-26 16:32:07 +02:00
query . bindValue ( QStringLiteral ( " :image " ) , image ) ;
2020-05-18 16:47:12 +02:00
Database : : instance ( ) . execute ( query ) ;
2020-05-30 17:33:08 +02:00
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Updated feed details: " < < feed - > title ( ) ;
2020-05-18 16:47:12 +02:00
2020-06-06 00:05:32 +02:00
Q_EMIT feedDetailsUpdated ( url , feed - > title ( ) , image , feed - > link ( ) , feed - > description ( ) , current ) ;
2020-05-26 16:32:07 +02:00
2021-04-20 09:55:57 +02:00
bool updatedEntries = false ;
2020-05-18 16:47:12 +02:00
for ( const auto & entry : feed - > items ( ) ) {
2021-04-11 13:30:21 +02:00
QCoreApplication : : processEvents ( ) ; // keep the main thread semi-responsive
2021-04-20 10:37:40 +02:00
bool isNewEntry = processEntry ( entry , url , isNewFeed ) ;
updatedEntries = updatedEntries | | isNewEntry ;
2021-04-09 17:03:02 +02:00
}
2021-04-09 21:43:29 +02:00
// Now mark the appropriate number of recent entries "new" and "read" only for new feeds
2021-04-09 20:28:36 +02:00
if ( isNewFeed ) {
query . prepare ( QStringLiteral ( " SELECT * FROM Entries WHERE feed=:feed ORDER BY updated DESC LIMIT :recentNew; " ) ) ;
query . bindValue ( QStringLiteral ( " :feed " ) , url ) ;
2021-05-05 14:04:47 +02:00
query . bindValue ( QStringLiteral ( " :recentNew " ) , 0 ) ; // hardcode to marking no episode as new on a new feed
2021-04-09 20:28:36 +02:00
Database : : instance ( ) . execute ( query ) ;
QSqlQuery updateQuery ;
while ( query . next ( ) ) {
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Marked as new: " < < query . value ( QStringLiteral ( " id " ) ) . toString ( ) ;
2021-04-09 20:28:36 +02:00
updateQuery . prepare ( QStringLiteral ( " UPDATE Entries SET read=:read, new=:new WHERE id=:id AND feed=:feed; " ) ) ;
updateQuery . bindValue ( QStringLiteral ( " :read " ) , false ) ;
updateQuery . bindValue ( QStringLiteral ( " :new " ) , true ) ;
updateQuery . bindValue ( QStringLiteral ( " :feed " ) , url ) ;
updateQuery . bindValue ( QStringLiteral ( " :id " ) , query . value ( QStringLiteral ( " id " ) ) . toString ( ) ) ;
Database : : instance ( ) . execute ( updateQuery ) ;
}
2021-05-07 10:26:58 +02:00
// Finally, reset the new flag to false now that the new feed has been fully processed
// If we would reset the flag sooner, then too many episodes will get flagged as new if
// the initial import gets interrupted somehow.
query . prepare ( QStringLiteral ( " UPDATE Feeds SET new=:new WHERE url=:url; " ) ) ;
query . bindValue ( QStringLiteral ( " :url " ) , url ) ;
query . bindValue ( QStringLiteral ( " :new " ) , false ) ;
Database : : instance ( ) . execute ( query ) ;
2020-05-18 16:47:12 +02:00
}
2020-05-26 16:32:07 +02:00
2021-05-01 21:35:37 +02:00
if ( updatedEntries | | isNewFeed )
Q_EMIT feedUpdated ( url ) ;
2021-04-20 09:55:57 +02:00
Q_EMIT feedUpdateFinished ( url ) ;
2020-05-18 16:47:12 +02:00
}
2021-05-01 21:00:12 +02:00
bool Fetcher : : processEntry ( Syndication : : ItemPtr entry , const QString & url , bool isNewFeed )
2020-05-18 16:47:12 +02:00
{
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Processing " < < entry - > title ( ) ;
2021-04-05 12:43:35 +02:00
// Retrieve "other" fields; this will include the "itunes" tags
QMultiMap < QString , QDomElement > otherItems = entry - > additionalProperties ( ) ;
2020-05-18 16:47:12 +02:00
QSqlQuery query ;
query . prepare ( QStringLiteral ( " SELECT COUNT (id) FROM Entries WHERE id=:id; " ) ) ;
query . bindValue ( QStringLiteral ( " :id " ) , entry - > id ( ) ) ;
Database : : instance ( ) . execute ( query ) ;
query . next ( ) ;
if ( query . value ( 0 ) . toInt ( ) ! = 0 )
2021-05-01 21:35:37 +02:00
return false ; // entry already exists
2020-05-18 16:47:12 +02:00
2021-04-09 17:03:02 +02:00
query . prepare ( QStringLiteral ( " INSERT INTO Entries VALUES (:feed, :id, :title, :content, :created, :updated, :link, :read, :new, :hasEnclosure, :image); " ) ) ;
2020-05-18 16:47:12 +02:00
query . bindValue ( QStringLiteral ( " :feed " ) , url ) ;
query . bindValue ( QStringLiteral ( " :id " ) , entry - > id ( ) ) ;
query . bindValue ( QStringLiteral ( " :title " ) , QTextDocumentFragment : : fromHtml ( entry - > title ( ) ) . toPlainText ( ) ) ;
query . bindValue ( QStringLiteral ( " :created " ) , static_cast < int > ( entry - > datePublished ( ) ) ) ;
query . bindValue ( QStringLiteral ( " :updated " ) , static_cast < int > ( entry - > dateUpdated ( ) ) ) ;
query . bindValue ( QStringLiteral ( " :link " ) , entry - > link ( ) ) ;
2021-02-21 19:54:10 +01:00
query . bindValue ( QStringLiteral ( " :hasEnclosure " ) , entry - > enclosures ( ) . length ( ) = = 0 ? 0 : 1 ) ;
2021-05-01 18:59:08 +00:00
query . bindValue ( QStringLiteral ( " :read " ) , isNewFeed ) ; // if new feed, then mark all as read
query . bindValue ( QStringLiteral ( " :new " ) , ! isNewFeed ) ; // if new feed, then mark none as new
2020-05-18 16:47:12 +02:00
if ( ! entry - > content ( ) . isEmpty ( ) )
query . bindValue ( QStringLiteral ( " :content " ) , entry - > content ( ) ) ;
else
query . bindValue ( QStringLiteral ( " :content " ) , entry - > description ( ) ) ;
2021-04-05 12:43:35 +02:00
// Look for image in itunes tags
QString image ;
if ( otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdimage " ) ) . hasAttribute ( QStringLiteral ( " href " ) ) ) {
image = otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdimage " ) ) . attribute ( QStringLiteral ( " href " ) ) ;
}
if ( image . startsWith ( QStringLiteral ( " / " ) ) )
image = QUrl ( url ) . adjusted ( QUrl : : RemovePath ) . toString ( ) + image ;
query . bindValue ( QStringLiteral ( " :image " ) , image ) ;
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Entry image found " < < image ;
2021-04-05 12:43:35 +02:00
2020-05-18 16:47:12 +02:00
Database : : instance ( ) . execute ( query ) ;
2021-04-05 12:43:35 +02:00
if ( entry - > authors ( ) . count ( ) > 0 ) {
for ( const auto & author : entry - > authors ( ) ) {
processAuthor ( url , entry - > id ( ) , author - > name ( ) , author - > uri ( ) , author - > email ( ) ) ;
}
} else {
// As fallback, check if there is itunes "author" information
QString authorName = otherItems . value ( QStringLiteral ( " http://www.itunes.com/dtds/podcast-1.0.dtdauthor " ) ) . text ( ) ;
2021-05-01 21:35:37 +02:00
if ( ! authorName . isEmpty ( ) )
processAuthor ( url , entry - > id ( ) , authorName , QLatin1String ( " " ) , QLatin1String ( " " ) ) ;
2020-05-18 16:47:12 +02:00
}
2020-05-18 17:02:46 +02:00
for ( const auto & enclosure : entry - > enclosures ( ) ) {
processEnclosure ( enclosure , entry , url ) ;
}
2021-04-05 12:43:35 +02:00
2021-04-03 11:44:08 +02:00
Q_EMIT entryAdded ( url , entry - > id ( ) ) ;
2021-04-20 09:55:57 +02:00
return true ; // this is a new entry
2020-05-18 16:47:12 +02:00
}
2021-04-05 12:43:35 +02:00
void Fetcher : : processAuthor ( const QString & url , const QString & entryId , const QString & authorName , const QString & authorUri , const QString & authorEmail )
2020-05-18 16:47:12 +02:00
{
QSqlQuery query ;
2021-04-19 20:17:34 +02:00
query . prepare ( QStringLiteral ( " SELECT COUNT (id) FROM Authors WHERE feed=:feed AND id=:id AND name=:name; " ) ) ;
2021-04-08 11:12:16 +02:00
query . bindValue ( QStringLiteral ( " :feed " ) , url ) ;
query . bindValue ( QStringLiteral ( " :id " ) , entryId ) ;
2021-04-19 20:17:34 +02:00
query . bindValue ( QStringLiteral ( " :name " ) , authorName ) ;
2021-04-08 11:12:16 +02:00
Database : : instance ( ) . execute ( query ) ;
query . next ( ) ;
if ( query . value ( 0 ) . toInt ( ) ! = 0 )
query . prepare ( QStringLiteral ( " UPDATE Authors SET feed=:feed, id=:id, name=:name, uri=:uri, email=:email WHERE feed=:feed AND id=:id; " ) ) ;
else
query . prepare ( QStringLiteral ( " INSERT INTO Authors VALUES(:feed, :id, :name, :uri, :email); " ) ) ;
2020-05-26 16:32:07 +02:00
query . bindValue ( QStringLiteral ( " :feed " ) , url ) ;
2020-05-30 17:33:08 +02:00
query . bindValue ( QStringLiteral ( " :id " ) , entryId ) ;
2021-04-05 12:43:35 +02:00
query . bindValue ( QStringLiteral ( " :name " ) , authorName ) ;
query . bindValue ( QStringLiteral ( " :uri " ) , authorUri ) ;
query . bindValue ( QStringLiteral ( " :email " ) , authorEmail ) ;
2020-05-18 16:47:12 +02:00
Database : : instance ( ) . execute ( query ) ;
}
2020-11-01 13:18:11 +01:00
void Fetcher : : processEnclosure ( Syndication : : EnclosurePtr enclosure , Syndication : : ItemPtr entry , const QString & feedUrl )
2020-05-18 17:02:46 +02:00
{
QSqlQuery query ;
2021-04-08 20:28:15 +02:00
query . prepare ( QStringLiteral ( " SELECT COUNT (id) FROM Enclosures WHERE feed=:feed AND id=:id; " ) ) ;
query . bindValue ( QStringLiteral ( " :feed " ) , feedUrl ) ;
query . bindValue ( QStringLiteral ( " :id " ) , entry - > id ( ) ) ;
Database : : instance ( ) . execute ( query ) ;
query . next ( ) ;
if ( query . value ( 0 ) . toInt ( ) ! = 0 )
2021-04-18 21:56:33 +02:00
query . prepare ( QStringLiteral ( " UPDATE Enclosures SET feed=:feed, id=:id, duration=:duration, size=:size, title=:title, type=:type, url=:url; " ) ) ;
2021-04-08 20:28:15 +02:00
else
2021-04-18 21:56:33 +02:00
query . prepare ( QStringLiteral ( " INSERT INTO Enclosures VALUES (:feed, :id, :duration, :size, :title, :type, :url, :playposition, :downloaded); " ) ) ;
2021-04-08 20:28:15 +02:00
2020-05-26 16:32:07 +02:00
query . bindValue ( QStringLiteral ( " :feed " ) , feedUrl ) ;
2020-05-18 17:02:46 +02:00
query . bindValue ( QStringLiteral ( " :id " ) , entry - > id ( ) ) ;
query . bindValue ( QStringLiteral ( " :duration " ) , enclosure - > duration ( ) ) ;
query . bindValue ( QStringLiteral ( " :size " ) , enclosure - > length ( ) ) ;
query . bindValue ( QStringLiteral ( " :title " ) , enclosure - > title ( ) ) ;
query . bindValue ( QStringLiteral ( " :type " ) , enclosure - > type ( ) ) ;
query . bindValue ( QStringLiteral ( " :url " ) , enclosure - > url ( ) ) ;
2021-04-08 20:28:15 +02:00
query . bindValue ( QStringLiteral ( " :playposition " ) , 0 ) ;
2021-06-23 21:11:04 +02:00
query . bindValue ( QStringLiteral ( " :downloaded " ) , Enclosure : : statusToDb ( Enclosure : : Downloadable ) ) ;
2020-05-18 17:02:46 +02:00
Database : : instance ( ) . execute ( query ) ;
}
2021-05-01 21:35:37 +02:00
QString Fetcher : : image ( const QString & url ) const
2020-04-25 22:16:19 +02:00
{
2021-04-07 12:47:46 +02:00
QString path = imagePath ( url ) ;
2020-05-10 23:25:23 +02:00
if ( QFileInfo : : exists ( path ) ) {
2021-05-01 21:35:37 +02:00
if ( QFileInfo ( path ) . size ( ) ! = 0 )
2021-04-05 14:15:44 +02:00
return path ;
2020-04-25 22:16:19 +02:00
}
2021-04-07 12:47:46 +02:00
download ( url , path ) ;
2020-05-18 21:20:23 +02:00
return QLatin1String ( " " ) ;
}
2021-04-07 12:47:46 +02:00
QNetworkReply * Fetcher : : download ( const QString & url , const QString & filePath ) const
2020-05-18 21:20:23 +02:00
{
2020-04-25 22:16:19 +02:00
QNetworkRequest request ( ( QUrl ( url ) ) ) ;
2021-04-22 17:05:33 +02:00
request . setTransferTimeout ( ) ;
2021-06-04 21:34:50 +02:00
QFile * file = new QFile ( filePath ) ;
2021-06-23 22:29:15 +02:00
int resumedAt = 0 ;
2021-06-04 21:34:50 +02:00
if ( file - > exists ( ) & & file - > size ( ) > 0 ) {
// try to resume download
2021-06-23 22:29:15 +02:00
resumedAt = file - > size ( ) ;
qCDebug ( kastsFetcher ) < < " Resuming download at " < < resumedAt < < " bytes " ;
QByteArray rangeHeaderValue = QByteArray ( " bytes= " ) + QByteArray : : number ( resumedAt ) + QByteArray ( " - " ) ;
2021-06-04 21:34:50 +02:00
request . setRawHeader ( QByteArray ( " Range " ) , rangeHeaderValue ) ;
file - > open ( QIODevice : : WriteOnly | QIODevice : : Append ) ;
} else {
2021-06-04 23:04:24 +02:00
qCDebug ( kastsFetcher ) < < " Starting new download " ;
2021-06-04 21:34:50 +02:00
file - > open ( QIODevice : : WriteOnly ) ;
}
2021-06-04 22:47:40 +02:00
QNetworkReply * headerReply = head ( request ) ;
connect ( headerReply , & QNetworkReply : : finished , this , [ = ] ( ) {
if ( headerReply - > isOpen ( ) ) {
int fileSize = headerReply - > header ( QNetworkRequest : : ContentLengthHeader ) . toInt ( ) ;
2021-06-23 22:29:15 +02:00
qCDebug ( kastsFetcher ) < < " Reported download size " < < fileSize ;
Q_EMIT downloadFileSizeUpdated ( url , fileSize , resumedAt ) ;
2021-06-04 22:47:40 +02:00
}
headerReply - > deleteLater ( ) ;
} ) ;
2020-07-02 19:14:07 +02:00
QNetworkReply * reply = get ( request ) ;
2021-06-04 21:34:50 +02:00
connect ( reply , & QNetworkReply : : readyRead , this , [ = ] ( ) {
if ( reply - > isOpen ( ) & & file ) {
QByteArray data = reply - > readAll ( ) ;
file - > write ( data ) ;
}
} ) ;
2021-02-21 19:54:10 +01:00
connect ( reply , & QNetworkReply : : finished , this , [ = ] ( ) {
2021-06-04 21:34:50 +02:00
if ( reply - > isOpen ( ) & & file ) {
2021-02-21 19:54:10 +01:00
QByteArray data = reply - > readAll ( ) ;
2021-06-04 21:34:50 +02:00
file - > write ( data ) ;
file - > close ( ) ;
2020-04-25 22:16:19 +02:00
2021-02-21 19:54:10 +01:00
Q_EMIT downloadFinished ( url ) ;
}
2021-06-04 21:34:50 +02:00
// clean up; close file if still open in case something has gone wrong
if ( file ) {
if ( file - > isOpen ( ) ) {
file - > close ( ) ;
}
delete file ;
}
2021-02-21 19:54:10 +01:00
reply - > deleteLater ( ) ;
2020-04-25 22:16:19 +02:00
} ) ;
2021-02-21 19:54:10 +01:00
return reply ;
2020-04-25 22:16:19 +02:00
}
2020-04-26 23:40:09 +02:00
2020-11-01 13:18:11 +01:00
void Fetcher : : removeImage ( const QString & url )
2020-04-26 23:40:09 +02:00
{
2021-06-05 20:12:42 +02:00
qCDebug ( kastsFetcher ) < < " Removing image " < < imagePath ( url ) ;
2021-04-07 12:47:46 +02:00
QFile ( imagePath ( url ) ) . remove ( ) ;
2020-04-26 23:40:09 +02:00
}
2021-04-07 12:47:46 +02:00
QString Fetcher : : imagePath ( const QString & url ) const
2020-04-26 23:40:09 +02:00
{
2021-04-07 12:47:46 +02:00
QString path = QStandardPaths : : writableLocation ( QStandardPaths : : CacheLocation ) + QStringLiteral ( " /images/ " ) ;
// Create path in cache if it doesn't exist yet
QFileInfo ( ) . absoluteDir ( ) . mkpath ( path ) ;
return path + QString : : fromStdString ( QCryptographicHash : : hash ( url . toUtf8 ( ) , QCryptographicHash : : Md5 ) . toHex ( ) . toStdString ( ) ) ;
2020-04-26 23:40:09 +02:00
}
2020-07-02 19:14:07 +02:00
2021-04-07 12:47:46 +02:00
QString Fetcher : : enclosurePath ( const QString & url ) const
{
QString path = QStandardPaths : : writableLocation ( QStandardPaths : : DataLocation ) + QStringLiteral ( " /enclosures/ " ) ;
// Create path in cache if it doesn't exist yet
QFileInfo ( ) . absoluteDir ( ) . mkpath ( path ) ;
return path + QString : : fromStdString ( QCryptographicHash : : hash ( url . toUtf8 ( ) , QCryptographicHash : : Md5 ) . toHex ( ) . toStdString ( ) ) ;
}
QNetworkReply * Fetcher : : get ( QNetworkRequest & request ) const
2020-07-02 19:14:07 +02:00
{
2021-06-04 22:47:40 +02:00
setHeader ( request ) ;
2020-07-02 19:14:07 +02:00
return manager - > get ( request ) ;
}
2021-06-04 22:47:40 +02:00
QNetworkReply * Fetcher : : head ( QNetworkRequest & request ) const
{
setHeader ( request ) ;
return manager - > head ( request ) ;
}
void Fetcher : : setHeader ( QNetworkRequest & request ) const
{
request . setRawHeader ( " User-Agent " , " Kasts/0.1; Syndication " ) ;
2021-06-23 22:29:15 +02:00
}