18 #include <QTemporaryFile>
22 #include <QFutureWatcher>
23 #include <QtConcurrentRun>
24 #include <QProcessEnvironment>
25 #include <QMutexLocker>
27 #include <QNetworkAccessManager>
28 #include <QNetworkReply>
29 #include <QDomDocument>
49 qint64 m_downloadedSize;
53 QString installedVersion()
const;
54 QString installedReleaseDate()
const;
55 bool isUpgradable()
const;
61 class FetchPreviewJob;
63 class NewstuffModelPrivate
78 NewstuffModel* m_parent;
102 Action m_currentAction;
110 #if QT_VERSION >= 0x050000
114 NewstuffModelPrivate( NewstuffModel* parent );
116 QIcon preview(
int index );
117 void setPreview(
int index,
const QIcon &previewIcon );
121 static bool canExecute(
const QString &executable );
129 void uninstall(
int index );
137 static NewstuffItem importNode(
const QDomNode &node );
139 bool isTransitioning(
int index )
const;
142 static void readValue(
const QDomNode &node,
const QString &key, T* target );
145 class FetchPreviewJob
148 FetchPreviewJob( NewstuffModelPrivate *modelPrivate,
int index );
153 NewstuffModelPrivate *
const m_modelPrivate;
157 NewstuffItem::NewstuffItem() : m_payloadSize( -2 ), m_downloadedSize( 0 )
162 QString NewstuffItem::installedVersion()
const
164 QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName(
"version" );
165 if ( nodes.
size() == 1 ) {
172 QString NewstuffItem::installedReleaseDate()
const
174 QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName(
"releasedate" );
175 if ( nodes.
size() == 1 ) {
182 bool NewstuffItem::isUpgradable()
const
184 bool installedOk, remoteOk;
185 double const installed = installedVersion().toDouble( &installedOk );
186 double const remote= m_version.toDouble( &remoteOk );
187 return installedOk && remoteOk && remote > installed;
193 QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName(
"installedfile" );
194 for (
int i=0; i<nodes.
count(); ++i ) {
200 bool NewstuffItem::deeperThan(
const QString &one,
const QString &two)
205 FetchPreviewJob::FetchPreviewJob( NewstuffModelPrivate *modelPrivate,
int index ) :
206 m_modelPrivate( modelPrivate ),
211 void FetchPreviewJob::run(
const QByteArray &data )
219 const QIcon previewIcon( pixmap );
220 m_modelPrivate->setPreview( m_index, previewIcon );
223 NewstuffModelPrivate::NewstuffModelPrivate( NewstuffModel* parent ) : m_parent( parent ),
224 m_networkAccessManager( 0 ), m_currentReply( 0 ), m_currentFile( 0 ),
225 m_idTag( NewstuffModel::PayloadTag ), m_currentAction( -1, Install ), m_unpackProcess( 0 )
230 QIcon NewstuffModelPrivate::preview(
int index )
232 if ( m_items.at( index ).m_preview.isNull() ) {
233 QPixmap dummyPixmap( 136, 136 );
234 dummyPixmap.fill( Qt::transparent );
235 setPreview( index,
QIcon( dummyPixmap ) );
237 m_networkJobs.insert( reply,
new FetchPreviewJob(
this, index ) );
240 Q_ASSERT( !m_items.at( index ).m_preview.isNull() );
242 return m_items.
at( index ).m_preview;
245 void NewstuffModelPrivate::setPreview(
int index,
const QIcon &previewIcon )
247 NewstuffItem &item = m_items[index];
248 item.m_preview = previewIcon;
249 const QModelIndex affected = m_parent->index( index );
250 emit m_parent->dataChanged( affected, affected );
253 void NewstuffModelPrivate::handleProviderData(
QNetworkReply *reply)
255 if ( reply->
operation() == QNetworkAccessManager::HeadOperation ) {
256 const QVariant redirectionAttribute = reply->
attribute( QNetworkRequest::RedirectionTargetAttribute );
257 if ( !redirectionAttribute.
isNull() ) {
258 for (
int i=0; i<m_items.size(); ++i ) {
259 NewstuffItem &item = m_items[i];
260 if ( item.m_payloadUrl == reply->
url() ) {
261 item.m_payloadUrl = redirectionAttribute.
toUrl();
268 QVariant const size = reply->
header( QNetworkRequest::ContentLengthHeader );
270 qint64 length = size.
value<qint64>();
271 for (
int i=0; i<m_items.size(); ++i ) {
272 NewstuffItem &item = m_items[i];
273 if ( item.m_payloadUrl == reply->
url() ) {
274 item.m_payloadSize = length;
276 emit m_parent->dataChanged( affected, affected );
283 FetchPreviewJob *
const job = m_networkJobs.take( reply );
286 const QVariant redirectionAttribute = reply->
attribute( QNetworkRequest::RedirectionTargetAttribute );
287 if ( !redirectionAttribute.
isNull() ) {
290 m_networkJobs.insert( redirectReply, job );
303 mDebug() <<
"Cannot parse newstuff xml data ";
311 #if QT_VERSION < 0x050000
316 for ( ; i < items.
length(); ++i ) {
317 m_items << importNode( items.
item( i ) );
323 bool NewstuffModelPrivate::canExecute(
const QString &executable )
328 if ( application.exists() ) {
336 void NewstuffModelPrivate::installMap()
338 if ( m_unpackProcess ) {
339 m_unpackProcess->close();
340 delete m_unpackProcess;
342 }
else if ( m_currentFile->fileName().endsWith(
QLatin1String(
"tar.gz" ) ) && canExecute(
"tar" ) ) {
345 m_parent, SLOT(contentsListed(
int)) );
347 m_unpackProcess->setWorkingDirectory( m_targetDirectory );
348 m_unpackProcess->start(
"tar", arguments );
350 if ( !m_currentFile->fileName().endsWith(
QLatin1String(
"tar.gz" ) ) ) {
351 mDebug() <<
"Can only handle tar.gz files";
353 mDebug() <<
"Cannot extract archive: tar executable not found in PATH.";
358 void NewstuffModelPrivate::updateModel()
360 QDomNodeList items = m_root.elementsByTagName(
"stuff" );
361 #if QT_VERSION < 0x050000
366 for ( ; i < items.
length(); ++i ) {
369 if ( matches.
size() == 1 ) {
372 for (
int j=0; j<m_items.size() && !found; ++j ) {
373 NewstuffItem &item = m_items[j];
375 item.m_registryNode = items.
item( i );
379 item.m_registryNode = items.
item( i );
386 NewstuffItem item = importNode( items.
item( i ) );
388 item.m_registryNode = items.
item( i );
390 item.m_registryNode = items.
item( i );
397 m_parent->beginResetModel();
398 m_parent->endResetModel();
401 void NewstuffModelPrivate::saveRegistry()
403 QFile output( m_registryFile );
404 if ( !output.open( QFile::WriteOnly ) ) {
405 mDebug() <<
"Cannot open " << m_registryFile <<
" for writing";
408 outStream << m_registryDocument.toString( 2 );
414 void NewstuffModelPrivate::uninstall(
int index )
419 QStringList const files = m_items[index].installedFiles();
420 foreach(
const QString &file, files ) {
428 qSort( directories.
begin(), directories.
end(), NewstuffItem::deeperThan );
429 foreach(
const QString &dir, directories ) {
433 m_items[index].m_registryNode.parentNode().removeChild( m_items[index].m_registryNode );
434 m_items[index].m_registryNode.clear();
440 if ( action == Append ) {
445 if ( !oldNode.
isNull() ) {
453 void NewstuffModelPrivate::readValue(
const QDomNode &node,
const QString &key, T* target )
456 if ( matches.
size() == 1 ) {
459 for (
int i=0; i<matches.
size(); ++i ) {
479 roles[Qt::DisplayRole] =
"display";
480 roles[
Name] =
"name";
497 #if QT_VERSION < 0x050000
500 d->m_roleNames = roles;
512 return d->m_items.size();
520 if ( index.
isValid() && index.
row() >= 0 && index.
row() < d->m_items.size() ) {
522 case Qt::DisplayRole:
return d->m_items.at( index.
row() ).m_name;
523 case Qt::DecorationRole:
return d->preview( index.
row() );
524 case Name:
return d->m_items.at( index.
row() ).m_name;
525 case Author:
return d->m_items.at( index.
row() ).m_author;
526 case License:
return d->m_items.at( index.
row() ).m_license;
527 case Summary:
return d->m_items.at( index.
row() ).m_summary;
528 case Version:
return d->m_items.at( index.
row() ).m_version;
529 case ReleaseDate:
return d->m_items.at( index.
row() ).m_releaseDate;
530 case Preview:
return d->m_items.at( index.
row() ).m_previewUrl;
531 case Payload:
return d->m_items.at( index.
row() ).m_payloadUrl;
535 case IsInstalled:
return !d->m_items.at( index.
row() ).m_registryNode.isNull();
537 case Category:
return d->m_items.at( index.
row() ).m_category;
540 qint64
const size = d->m_items.at( index.
row() ).m_payloadSize;
541 QUrl const url = d->m_items.at( index.
row() ).m_payloadUrl;
542 if ( size < -1 && !url.
isEmpty() ) {
543 d->m_items[index.
row()].m_payloadSize = -1;
547 return qMax<qint64>( -1, size );
556 #if QT_VERSION >= 0x050000
559 return d->m_roleNames;
571 if ( downloadUrl == d->m_provider ) {
575 d->m_provider = downloadUrl;
582 return d->m_provider;
587 if ( targetDirectory != d->m_targetDirectory ) {
589 if ( !targetDir.
exists() ) {
591 qDebug() <<
"Failed to create directory " << targetDirectory <<
", newstuff installation might fail.";
602 return d->m_targetDirectory;
612 if ( d->m_registryFile != registryFile ) {
618 if ( !inputFile.
exists() ) {
620 d->m_registryDocument =
QDomDocument(
"khotnewstuff3" );
621 QDomProcessingInstruction header = d->m_registryDocument.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"utf-8\"" );
622 d->m_registryDocument.appendChild( header );
623 d->m_root = d->m_registryDocument.createElement(
"hotnewstuffregistry" );
624 d->m_registryDocument.appendChild( d->m_root );
626 QFile input( registryFile );
627 if ( !input.
open( QFile::ReadOnly ) ) {
632 if ( !d->m_registryDocument.setContent( &input ) ) {
637 d->m_root = d->m_registryDocument.documentElement();
646 return d->m_registryFile;
651 if ( index < 0 || index >= d->m_items.size() ) {
658 if ( d->m_actionQueue.contains( action ) ) {
661 d->m_actionQueue << action;
669 if ( idx < 0 || idx >= d->m_items.size() ) {
673 if ( d->m_items[idx].m_registryNode.isNull() ) {
680 if ( d->m_actionQueue.contains( action ) ) {
683 d->m_actionQueue << action;
691 if ( !d->isTransitioning( index ) ) {
697 if ( d->m_currentAction.first == index ) {
698 if ( d->m_currentAction.second == NewstuffModelPrivate::Install ) {
699 if ( d->m_currentReply ) {
700 d->m_currentReply->abort();
701 d->m_currentReply->deleteLater();
702 d->m_currentReply = 0;
705 if ( d->m_unpackProcess ) {
706 d->m_unpackProcess->terminate();
707 d->m_unpackProcess->deleteLater();
708 d->m_unpackProcess = 0;
711 if ( d->m_currentFile ) {
712 d->m_currentFile->deleteLater();
713 d->m_currentFile = 0;
716 d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
724 if ( d->m_currentAction.second == NewstuffModelPrivate::Install ) {
726 d->m_actionQueue.removeAll( install );
730 d->m_actionQueue.removeAll( uninstall );
739 void NewstuffModel::updateProgress( qint64 bytesReceived, qint64 bytesTotal )
741 qreal
const progress = qBound<qreal>( 0.0, 0.9 * bytesReceived / qreal( bytesTotal ), 1.0 );
743 NewstuffItem &item = d->m_items[d->m_currentAction.first];
744 item.m_payloadSize = bytesTotal;
745 if ( qreal(bytesReceived-item.m_downloadedSize)/bytesTotal >= 0.01 || progress >= 0.9 ) {
747 item.m_downloadedSize = bytesReceived;
753 void NewstuffModel::retrieveData()
755 if ( d->m_currentReply && d->m_currentReply->isReadable() ) {
757 const QVariant redirectionAttribute = d->m_currentReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
758 if ( !redirectionAttribute.
isNull() ) {
759 d->m_currentReply = d->m_networkAccessManager.get(
QNetworkRequest( redirectionAttribute.
toUrl() ) );
760 QObject::connect( d->m_currentReply, SIGNAL(readyRead()),
this, SLOT(retrieveData()) );
761 QObject::connect( d->m_currentReply, SIGNAL(readChannelFinished()),
this, SLOT(retrieveData()) );
762 QObject::connect( d->m_currentReply, SIGNAL(downloadProgress(qint64,qint64)),
763 this, SLOT(updateProgress(qint64,qint64)) );
765 d->m_currentFile->write( d->m_currentReply->readAll() );
766 if ( d->m_currentReply->isFinished() ) {
767 d->m_currentReply->deleteLater();
768 d->m_currentReply = 0;
769 d->m_currentFile->flush();
776 void NewstuffModel::mapInstalled(
int exitStatus )
778 if ( d->m_unpackProcess ) {
779 d->m_unpackProcess->deleteLater();
780 d->m_unpackProcess = 0;
783 if ( d->m_currentFile ) {
784 d->m_currentFile->deleteLater();
785 d->m_currentFile = 0;
789 d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
790 if ( exitStatus == 0 ) {
793 mDebug() <<
"Process exit status " << exitStatus <<
" indicates an error.";
794 emit
installationFailed( d->m_currentAction.first ,
QString(
"Unable to unpack file. Process exited with status code %1." ).arg( exitStatus ) );
800 d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
806 void NewstuffModel::mapUninstalled()
813 d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
819 void NewstuffModel::contentsListed(
int exitStatus )
822 if ( exitStatus == 0 ) {
823 if ( !d->m_registryFile.isEmpty() ) {
824 NewstuffItem &item = d->m_items[d->m_currentAction.first];
825 QDomNode node = item.m_registryNode;
826 NewstuffModelPrivate::NodeAction action = node.
isNull() ? NewstuffModelPrivate::Append : NewstuffModelPrivate::Replace;
828 node = d->m_root.appendChild( d->m_registryDocument.createElement(
"stuff" ) );
832 d->changeNode( node, d->m_registryDocument,
"name", item.m_name, action );
833 d->changeNode( node, d->m_registryDocument,
"providerid", d->m_provider, action );
834 d->changeNode( node, d->m_registryDocument,
"author", item.m_author, action );
835 d->changeNode( node, d->m_registryDocument,
"homepage",
QString(), action );
836 d->changeNode( node, d->m_registryDocument,
"licence", item.m_license, action );
837 d->changeNode( node, d->m_registryDocument,
"version", item.m_version, action );
838 QString const itemId = d->m_idTag ==
PayloadTag ? item.m_payloadUrl.toString() : item.m_name;
839 d->changeNode( node, d->m_registryDocument,
"id", itemId, action );
840 d->changeNode( node, d->m_registryDocument,
"releasedate", item.m_releaseDate, action );
841 d->changeNode( node, d->m_registryDocument,
"summary", item.m_summary, action );
842 d->changeNode( node, d->m_registryDocument,
"changelog",
QString(), action );
843 d->changeNode( node, d->m_registryDocument,
"preview", item.m_previewUrl.toString(), action );
844 d->changeNode( node, d->m_registryDocument,
"previewBig", item.m_previewUrl.toString(), action );
845 d->changeNode( node, d->m_registryDocument,
"payload", item.m_payloadUrl.toString(), action );
846 d->changeNode( node, d->m_registryDocument,
"status",
"installed", action );
847 d->m_items[d->m_currentAction.first].m_registryNode = node;
850 while ( hasChildren ) {
853 hasChildren = !fileList.
isEmpty();
854 for (
int i=0; i<fileList.
count(); ++i ) {
859 QStringList const files =
QString( d->m_unpackProcess->readAllStandardOutput() ).split(
'\n', QString::SkipEmptyParts );
860 foreach(
const QString &file, files ) {
861 QDomNode fileNode = node.
appendChild( d->m_registryDocument.createElement(
"installedfile" ) );
862 fileNode.
appendChild( d->m_registryDocument.createTextNode( d->m_targetDirectory +
'/' + file ) );
869 this, SLOT(contentsListed(
int)) );
871 this, SLOT(mapInstalled(
int)) );
873 d->m_unpackProcess->start(
"tar", arguments );
875 mDebug() <<
"Process exit status " << exitStatus <<
" indicates an error.";
876 emit
installationFailed( d->m_currentAction.first ,
QString(
"Unable to list file contents. Process exited with status code %1." ).arg( exitStatus ) );
880 d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
886 void NewstuffModelPrivate::processQueue()
888 if ( m_actionQueue.empty() || m_currentAction.first >= 0 ) {
894 m_currentAction = m_actionQueue.takeFirst();
896 if ( m_currentAction.second == Install ) {
897 if ( !m_currentFile ) {
898 QFileInfo const file = m_items.
at( m_currentAction.first ).m_payloadUrl.path();
902 if ( m_currentFile->open() ) {
903 QUrl const payload = m_items.at( m_currentAction.first ).m_payloadUrl;
904 m_currentReply = m_networkAccessManager.get(
QNetworkRequest( payload ) );
905 QObject::connect( m_currentReply, SIGNAL(readyRead()), m_parent, SLOT(retrieveData()) );
906 QObject::connect( m_currentReply, SIGNAL(readChannelFinished()), m_parent, SLOT(retrieveData()) );
908 m_parent, SLOT(updateProgress(qint64,qint64)) );
911 mDebug() <<
"Failed to write to " << m_currentFile->fileName();
916 QObject::connect( watcher, SIGNAL(finished()), m_parent, SLOT(mapUninstalled()) );
917 QObject::connect( watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()) );
924 NewstuffItem NewstuffModelPrivate::importNode(
const QDomNode &node)
928 readValue<QString>( node,
"name", &item.m_name );
929 readValue<QString>( node,
"author", &item.m_author );
930 readValue<QString>( node,
"licence", &item.m_license );
931 readValue<QString>( node,
"summary", &item.m_summary );
932 readValue<QString>( node,
"version", &item.m_version );
933 readValue<QString>( node,
"releasedate", &item.m_releaseDate );
934 readValue<QUrl>( node,
"preview", &item.m_previewUrl );
935 readValue<QUrl>( node,
"payload", &item.m_payloadUrl );
939 bool NewstuffModelPrivate::isTransitioning(
int index )
const
941 if ( m_currentAction.first == index ) {
945 foreach(
const Action &action, m_actionQueue ) {
946 if ( action.first == index ) {
956 #include "NewstuffModel.moc"
QDomNodeList elementsByTagName(const QString &tagname) const
void installationProgressed(int newstuffindex, qreal progress)
QDomNode item(int index) const
void targetDirectoryChanged()
QImage fromData(const uchar *data, int size, const char *format)
void setRoleNames(const QHash< int, QByteArray > &roleNames)
bool contains(const QString &name) const
QDomNode appendChild(const QDomNode &newChild)
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
const QHash< int, QByteArray > & roleNames() const
void setProvider(const QString &downloadUrl)
Add a newstuff provider.
QPixmap fromImage(const QImage &image, QFlags< Qt::ImageConversionFlag > flags)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Overload of QAbstractListModel.
static QString localPath()
QDomElement documentElement() const
void installationFinished(int newstuffindex)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString value(const QString &name, const QString &defaultValue) const
NewstuffModel(QObject *parent=0)
Constructor.
QDomElement toElement() const
bool rmdir(const QString &dirName) const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
void setTargetDirectory(const QString &targetDirectory)
void setAttribute(const QString &name, const QString &value)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
void installationFailed(int newstuffindex, const QString &error)
QString absoluteFilePath() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QFuture< T > run(Function function,...)
void uninstall(int index)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void setFuture(const QFuture< T > &future)
QDomText createTextNode(const QString &value)
QDomNode namedItem(const QString &name) const
QDomNode removeChild(const QDomNode &oldChild)
QDomNode namedItem(const QString &name) const
QNetworkAccessManager::Operation operation() const
QDomNode firstChild() const
QString mid(int position, int n) const
virtual bool hasChildren(const QModelIndex &parent) const
const QChar at(int position) const
QVariant attribute(QNetworkRequest::Attribute code) const
void setRegistryFile(const QString ®istryFile, IdTag idTag=PayloadTag)
int rowCount(const QModelIndex &parent=QModelIndex()) const
Overload of QAbstractListModel.
QProcessEnvironment systemEnvironment()
QDomElement createElement(const QString &tagName)
~NewstuffModel()
Destructor.
The class that displays copyright info.
QString absolutePath() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QDebug mDebug()
a function to replace qDebug() in Marble library code
QDomNamedNodeMap attributes() const
QString targetDirectory() const
QString registryFile() const
bool mkpath(const QString &dirPath) const
void registryFileChanged()
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QDomNode at(int index) const
void uninstallationFinished(int newstuffindex)