• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdeedu API Reference
  • KDE Home
  • Contact Us
 

marble

  • sources
  • kde-4.14
  • kdeedu
  • marble
  • src
  • lib
  • marble
NewstuffModel.cpp
Go to the documentation of this file.
1 //
2 // This file is part of the Marble Virtual Globe.
3 //
4 // This program is free software licensed under the GNU LGPL. You can
5 // find a copy of this license in LICENSE.txt in the top directory of
6 // the source code.
7 //
8 // Copyright 2012 Dennis Nienhüser <earthwings@gentoo.org>
9 //
10 
11 #include "NewstuffModel.h"
12 
13 #include "MarbleDebug.h"
14 #include "MarbleDirs.h"
15 
16 #include <QUrl>
17 #include <QVector>
18 #include <QTemporaryFile>
19 #include <QDir>
20 #include <QFuture>
21 #include <QPair>
22 #include <QFutureWatcher>
23 #include <QtConcurrentRun>
24 #include <QProcessEnvironment>
25 #include <QMutexLocker>
26 #include <QIcon>
27 #include <QNetworkAccessManager>
28 #include <QNetworkReply>
29 #include <QDomDocument>
30 
31 namespace Marble
32 {
33 
34 class NewstuffItem
35 {
36 public:
37  QString m_category;
38  QString m_name;
39  QString m_author;
40  QString m_license;
41  QString m_summary;
42  QString m_version;
43  QString m_releaseDate;
44  QUrl m_previewUrl;
45  QIcon m_preview;
46  QUrl m_payloadUrl;
47  QDomNode m_registryNode;
48  qint64 m_payloadSize;
49  qint64 m_downloadedSize;
50 
51  NewstuffItem();
52 
53  QString installedVersion() const;
54  QString installedReleaseDate() const;
55  bool isUpgradable() const;
56  QStringList installedFiles() const;
57 
58  static bool deeperThan( const QString &one, const QString &two );
59 };
60 
61 class FetchPreviewJob;
62 
63 class NewstuffModelPrivate
64 {
65 public:
66  enum NodeAction {
67  Append,
68  Replace
69  };
70 
71  enum UserAction {
72  Install,
73  Uninstall
74  };
75 
76  typedef QPair<int, UserAction> Action;
77 
78  NewstuffModel* m_parent;
79 
80  QVector<NewstuffItem> m_items;
81 
82  QNetworkAccessManager m_networkAccessManager;
83 
84  QString m_provider;
85 
86  QMap<QNetworkReply *, FetchPreviewJob *> m_networkJobs;
87 
88  QNetworkReply* m_currentReply;
89 
90  QTemporaryFile* m_currentFile;
91 
92  QString m_targetDirectory;
93 
94  QString m_registryFile;
95 
96  NewstuffModel::IdTag m_idTag;
97 
98  QDomDocument m_registryDocument;
99 
100  QDomElement m_root;
101 
102  Action m_currentAction;
103 
104  QProcess* m_unpackProcess;
105 
106  QMutex m_mutex;
107 
108  QList<Action> m_actionQueue;
109 
110 #if QT_VERSION >= 0x050000
111  QHash<int, QByteArray> m_roleNames;
112 #endif
113 
114  NewstuffModelPrivate( NewstuffModel* parent );
115 
116  QIcon preview( int index );
117  void setPreview( int index, const QIcon &previewIcon );
118 
119  void handleProviderData( QNetworkReply* reply );
120 
121  static bool canExecute( const QString &executable );
122 
123  void installMap();
124 
125  void updateModel();
126 
127  void saveRegistry();
128 
129  void uninstall( int index );
130 
131  static void changeNode( QDomNode &node, QDomDocument &domDocument, const QString &key, const QString &value, NodeAction action );
132 
133  void readInstalledFiles( QStringList* target, const QDomNode &node );
134 
135  void processQueue();
136 
137  static NewstuffItem importNode( const QDomNode &node );
138 
139  bool isTransitioning( int index ) const;
140 
141  template<class T>
142  static void readValue( const QDomNode &node, const QString &key, T* target );
143 };
144 
145 class FetchPreviewJob
146 {
147 public:
148  FetchPreviewJob( NewstuffModelPrivate *modelPrivate, int index );
149 
150  void run( const QByteArray &data );
151 
152 private:
153  NewstuffModelPrivate *const m_modelPrivate;
154  const int m_index;
155 };
156 
157 NewstuffItem::NewstuffItem() : m_payloadSize( -2 ), m_downloadedSize( 0 )
158 {
159  // nothing to do
160 }
161 
162 QString NewstuffItem::installedVersion() const
163 {
164  QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName( "version" );
165  if ( nodes.size() == 1 ) {
166  return nodes.at( 0 ).toElement().text();
167  }
168 
169  return QString();
170 }
171 
172 QString NewstuffItem::installedReleaseDate() const
173 {
174  QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName( "releasedate" );
175  if ( nodes.size() == 1 ) {
176  return nodes.at( 0 ).toElement().text();
177  }
178 
179  return QString();
180 }
181 
182 bool NewstuffItem::isUpgradable() const
183 {
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;
188 }
189 
190 QStringList NewstuffItem::installedFiles() const
191 {
192  QStringList result;
193  QDomNodeList const nodes = m_registryNode.toElement().elementsByTagName( "installedfile" );
194  for ( int i=0; i<nodes.count(); ++i ) {
195  result << nodes.at( i ).toElement().text();
196  }
197  return result;
198 }
199 
200 bool NewstuffItem::deeperThan(const QString &one, const QString &two)
201 {
202  return one.length() > two.length();
203 }
204 
205 FetchPreviewJob::FetchPreviewJob( NewstuffModelPrivate *modelPrivate, int index ) :
206  m_modelPrivate( modelPrivate ),
207  m_index( index )
208 {
209 }
210 
211 void FetchPreviewJob::run( const QByteArray &data )
212 {
213  const QImage image = QImage::fromData( data );
214 
215  if ( image.isNull() )
216  return;
217 
218  const QPixmap pixmap = QPixmap::fromImage( image );
219  const QIcon previewIcon( pixmap );
220  m_modelPrivate->setPreview( m_index, previewIcon );
221 }
222 
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 )
226 {
227  // nothing to do
228 }
229 
230 QIcon NewstuffModelPrivate::preview( int index )
231 {
232  if ( m_items.at( index ).m_preview.isNull() ) {
233  QPixmap dummyPixmap( 136, 136 );
234  dummyPixmap.fill( Qt::transparent );
235  setPreview( index, QIcon( dummyPixmap ) );
236  QNetworkReply *reply = m_networkAccessManager.get( QNetworkRequest( m_items.at( index ).m_previewUrl ) );
237  m_networkJobs.insert( reply, new FetchPreviewJob( this, index ) );
238  }
239 
240  Q_ASSERT( !m_items.at( index ).m_preview.isNull() );
241 
242  return m_items.at( index ).m_preview;
243 }
244 
245 void NewstuffModelPrivate::setPreview( int index, const QIcon &previewIcon )
246 {
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 );
251 }
252 
253 void NewstuffModelPrivate::handleProviderData(QNetworkReply *reply)
254 {
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();
262  }
263  }
264  m_networkAccessManager.head( QNetworkRequest( redirectionAttribute.toUrl() ) );
265  return;
266  }
267 
268  QVariant const size = reply->header( QNetworkRequest::ContentLengthHeader );
269  if ( size.isValid() ) {
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;
275  QModelIndex const affected = m_parent->index( i );
276  emit m_parent->dataChanged( affected, affected );
277  }
278  }
279  }
280  return;
281  }
282 
283  FetchPreviewJob *const job = m_networkJobs.take( reply );
284 
285  // check if we are redirected
286  const QVariant redirectionAttribute = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
287  if ( !redirectionAttribute.isNull() ) {
288  QNetworkReply *redirectReply = m_networkAccessManager.get( QNetworkRequest( QUrl( redirectionAttribute.toUrl() ) ) );
289  if ( job ) {
290  m_networkJobs.insert( redirectReply, job );
291  }
292  return;
293  }
294 
295  if ( job ) {
296  job->run( reply->readAll() );
297  delete job;
298  return;
299  }
300 
301  QDomDocument xml;
302  if ( !xml.setContent( reply->readAll() ) ) {
303  mDebug() << "Cannot parse newstuff xml data ";
304  return;
305  }
306 
307  m_items.clear();
308 
309  QDomElement root = xml.documentElement();
310  QDomNodeList items = root.elementsByTagName( "stuff" );
311 #if QT_VERSION < 0x050000
312  unsigned int i=0;
313 #else
314  int i=0;
315 #endif
316  for ( ; i < items.length(); ++i ) {
317  m_items << importNode( items.item( i ) );
318  }
319 
320  updateModel();
321 }
322 
323 bool NewstuffModelPrivate::canExecute( const QString &executable )
324 {
325  QString path = QProcessEnvironment::systemEnvironment().value( "PATH", "/usr/local/bin:/usr/bin:/bin" );
326  foreach( const QString &dir, path.split( QLatin1Char( ':' ) ) ) {
327  QFileInfo application( QDir( dir ), executable );
328  if ( application.exists() ) {
329  return true;
330  }
331  }
332 
333  return false;
334 }
335 
336 void NewstuffModelPrivate::installMap()
337 {
338  if ( m_unpackProcess ) {
339  m_unpackProcess->close();
340  delete m_unpackProcess;
341  m_unpackProcess = 0;
342  } else if ( m_currentFile->fileName().endsWith( QLatin1String( "tar.gz" ) ) && canExecute( "tar" ) ) {
343  m_unpackProcess = new QProcess;
344  QObject::connect( m_unpackProcess, SIGNAL(finished(int)),
345  m_parent, SLOT(contentsListed(int)) );
346  QStringList arguments = QStringList() << "-t" << "-z" << "-f" << m_currentFile->fileName();
347  m_unpackProcess->setWorkingDirectory( m_targetDirectory );
348  m_unpackProcess->start( "tar", arguments );
349  } else {
350  if ( !m_currentFile->fileName().endsWith( QLatin1String( "tar.gz" ) ) ) {
351  mDebug() << "Can only handle tar.gz files";
352  } else {
353  mDebug() << "Cannot extract archive: tar executable not found in PATH.";
354  }
355  }
356 }
357 
358 void NewstuffModelPrivate::updateModel()
359 {
360  QDomNodeList items = m_root.elementsByTagName( "stuff" );
361 #if QT_VERSION < 0x050000
362  unsigned int i=0;
363 #else
364  int i=0;
365 #endif
366  for ( ; i < items.length(); ++i ) {
367  QString const key = m_idTag == NewstuffModel::PayloadTag ? "payload" : "name";
368  QDomNodeList matches = items.item( i ).toElement().elementsByTagName( key );
369  if ( matches.size() == 1 ) {
370  QString const value = matches.at( 0 ).toElement().text();
371  bool found = false;
372  for ( int j=0; j<m_items.size() && !found; ++j ) {
373  NewstuffItem &item = m_items[j];
374  if ( m_idTag == NewstuffModel::PayloadTag && item.m_payloadUrl == value ) {
375  item.m_registryNode = items.item( i );
376  found = true;
377  }
378  if ( m_idTag == NewstuffModel::NameTag && item.m_name == value ) {
379  item.m_registryNode = items.item( i );
380  found = true;
381  }
382  }
383 
384  if ( !found ) {
385  // Not found in newstuff or newstuff not there yet
386  NewstuffItem item = importNode( items.item( i ) );
387  if ( m_idTag == NewstuffModel::PayloadTag ) {
388  item.m_registryNode = items.item( i );
389  } else if ( m_idTag == NewstuffModel::NameTag ) {
390  item.m_registryNode = items.item( i );
391  }
392  m_items << item;
393  }
394  }
395  }
396 
397  m_parent->beginResetModel();
398  m_parent->endResetModel();
399 }
400 
401 void NewstuffModelPrivate::saveRegistry()
402 {
403  QFile output( m_registryFile );
404  if ( !output.open( QFile::WriteOnly ) ) {
405  mDebug() << "Cannot open " << m_registryFile << " for writing";
406  } else {
407  QTextStream outStream( &output );
408  outStream << m_registryDocument.toString( 2 );
409  outStream.flush();
410  output.close();
411  }
412 }
413 
414 void NewstuffModelPrivate::uninstall( int index )
415 {
416  // Delete all files first, then directories (deeper ones first)
417 
418  QStringList directories;
419  QStringList const files = m_items[index].installedFiles();
420  foreach( const QString &file, files ) {
421  if ( file.endsWith( '/' ) ) {
422  directories << file;
423  } else {
424  QFile::remove( file );
425  }
426  }
427 
428  qSort( directories.begin(), directories.end(), NewstuffItem::deeperThan );
429  foreach( const QString &dir, directories ) {
430  QDir::root().rmdir( dir );
431  }
432 
433  m_items[index].m_registryNode.parentNode().removeChild( m_items[index].m_registryNode );
434  m_items[index].m_registryNode.clear();
435  saveRegistry();
436 }
437 
438 void NewstuffModelPrivate::changeNode( QDomNode &node, QDomDocument &domDocument, const QString &key, const QString &value, NodeAction action )
439 {
440  if ( action == Append ) {
441  QDomNode newNode = node.appendChild( domDocument.createElement( key ) );
442  newNode.appendChild( domDocument.createTextNode( value ) );
443  } else {
444  QDomNode oldNode = node.namedItem( key );
445  if ( !oldNode.isNull() ) {
446  oldNode.removeChild( oldNode.firstChild() );
447  oldNode.appendChild( domDocument.createTextNode( value ) );
448  }
449  }
450 }
451 
452 template<class T>
453 void NewstuffModelPrivate::readValue( const QDomNode &node, const QString &key, T* target )
454 {
455  QDomNodeList matches = node.toElement().elementsByTagName( key );
456  if ( matches.size() == 1 ) {
457  *target = matches.at( 0 ).toElement().text();
458  } else {
459  for ( int i=0; i<matches.size(); ++i ) {
460  if ( matches.at( i ).attributes().contains( "lang" ) &&
461  matches.at( i ).attributes().namedItem( "lang").toAttr().value() == "en" ) {
462  *target = matches.at( i ).toElement().text();
463  return;
464  }
465  }
466  }
467 }
468 
469 NewstuffModel::NewstuffModel( QObject *parent ) :
470  QAbstractListModel( parent ), d( new NewstuffModelPrivate( this ) )
471 {
472  setTargetDirectory( MarbleDirs::localPath() + "/maps" );
473  // no default registry file
474 
475  connect( &d->m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
476  this, SLOT(handleProviderData(QNetworkReply*)) );
477 
478  QHash<int,QByteArray> roles;
479  roles[Qt::DisplayRole] = "display";
480  roles[Name] = "name";
481  roles[Author] = "author";
482  roles[License] = "license";
483  roles[Summary] = "summary";
484  roles[Version] = "version";
485  roles[ReleaseDate] = "releasedate";
486  roles[Preview] = "preview";
487  roles[Payload] = "payload";
488  roles[InstalledVersion] = "installedversion";
489  roles[InstalledReleaseDate] = "installedreleasedate";
490  roles[InstalledFiles] = "installedfiles";
491  roles[IsInstalled] = "installed";
492  roles[IsUpgradable] = "upgradable";
493  roles[Category] = "category";
494  roles[IsTransitioning] = "transitioning";
495  roles[PayloadSize] = "size";
496  roles[DownloadedSize] = "downloaded";
497 #if QT_VERSION < 0x050000
498  setRoleNames( roles );
499 #else
500  d->m_roleNames = roles;
501 #endif
502 }
503 
504 NewstuffModel::~NewstuffModel()
505 {
506  delete d;
507 }
508 
509 int NewstuffModel::rowCount ( const QModelIndex &parent ) const
510 {
511  if ( !parent.isValid() ) {
512  return d->m_items.size();
513  }
514 
515  return 0;
516 }
517 
518 QVariant NewstuffModel::data ( const QModelIndex &index, int role ) const
519 {
520  if ( index.isValid() && index.row() >= 0 && index.row() < d->m_items.size() ) {
521  switch ( role ) {
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;
532  case InstalledVersion: return d->m_items.at( index.row() ).installedVersion();
533  case InstalledReleaseDate: return d->m_items.at( index.row() ).installedReleaseDate();
534  case InstalledFiles: return d->m_items.at( index.row() ).installedFiles();
535  case IsInstalled: return !d->m_items.at( index.row() ).m_registryNode.isNull();
536  case IsUpgradable: return d->m_items.at( index.row() ).isUpgradable();
537  case Category: return d->m_items.at( index.row() ).m_category;
538  case IsTransitioning: return d->isTransitioning( index.row() );
539  case PayloadSize: {
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; // prevent several head requests for the same item
544  d->m_networkAccessManager.head( QNetworkRequest( url ) );
545  }
546 
547  return qMax<qint64>( -1, size );
548  }
549  case DownloadedSize: return d->m_items.at( index.row() ).m_downloadedSize;
550  }
551  }
552 
553  return QVariant();
554 }
555 
556 #if QT_VERSION >= 0x050000
557 QHash<int, QByteArray> NewstuffModel::roleNames() const
558 {
559  return d->m_roleNames;
560 }
561 #endif
562 
563 
564 int NewstuffModel::count() const
565 {
566  return rowCount();
567 }
568 
569 void NewstuffModel::setProvider( const QString &downloadUrl )
570 {
571  if ( downloadUrl == d->m_provider ) {
572  return;
573  }
574 
575  d->m_provider = downloadUrl;
576  emit providerChanged();
577  d->m_networkAccessManager.get( QNetworkRequest( QUrl( downloadUrl ) ) );
578 }
579 
580 QString NewstuffModel::provider() const
581 {
582  return d->m_provider;
583 }
584 
585 void NewstuffModel::setTargetDirectory( const QString &targetDirectory )
586 {
587  if ( targetDirectory != d->m_targetDirectory ) {
588  QFileInfo targetDir( targetDirectory );
589  if ( !targetDir.exists() ) {
590  if ( !QDir::root().mkpath( targetDir.absoluteFilePath() ) ) {
591  qDebug() << "Failed to create directory " << targetDirectory << ", newstuff installation might fail.";
592  }
593  }
594 
595  d->m_targetDirectory = targetDirectory;
596  emit targetDirectoryChanged();
597  }
598 }
599 
600 QString NewstuffModel::targetDirectory() const
601 {
602  return d->m_targetDirectory;
603 }
604 
605 void NewstuffModel::setRegistryFile( const QString &filename, IdTag idTag )
606 {
607  QString registryFile = filename;
608  if ( registryFile.startsWith( '~' ) && registryFile.length() > 1 ) {
609  registryFile = QDir::homePath() + registryFile.mid( 1 );
610  }
611 
612  if ( d->m_registryFile != registryFile ) {
613  d->m_registryFile = registryFile;
614  d->m_idTag = idTag;
615  emit registryFileChanged();
616 
617  QFileInfo inputFile( registryFile );
618  if ( !inputFile.exists() ) {
619  QDir::root().mkpath( inputFile.absolutePath() );
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 );
625  } else {
626  QFile input( registryFile );
627  if ( !input.open( QFile::ReadOnly ) ) {
628  mDebug() << "Cannot open newstuff registry " << registryFile;
629  return;
630  }
631 
632  if ( !d->m_registryDocument.setContent( &input ) ) {
633  mDebug() << "Cannot parse newstuff registry " << registryFile;
634  return;
635  }
636  input.close();
637  d->m_root = d->m_registryDocument.documentElement();
638  }
639 
640  d->updateModel();
641  }
642 }
643 
644 QString NewstuffModel::registryFile() const
645 {
646  return d->m_registryFile;
647 }
648 
649 void NewstuffModel::install( int index )
650 {
651  if ( index < 0 || index >= d->m_items.size() ) {
652  return;
653  }
654 
655  NewstuffModelPrivate::Action action( index, NewstuffModelPrivate::Install );
656  { // <-- do not remove, mutex locker scope
657  QMutexLocker locker( &d->m_mutex );
658  if ( d->m_actionQueue.contains( action ) ) {
659  return;
660  }
661  d->m_actionQueue << action;
662  }
663 
664  d->processQueue();
665 }
666 
667 void NewstuffModel::uninstall( int idx )
668 {
669  if ( idx < 0 || idx >= d->m_items.size() ) {
670  return;
671  }
672 
673  if ( d->m_items[idx].m_registryNode.isNull() ) {
674  emit uninstallationFinished( idx );
675  }
676 
677  NewstuffModelPrivate::Action action( idx, NewstuffModelPrivate::Uninstall );
678  { // <-- do not remove, mutex locker scope
679  QMutexLocker locker( &d->m_mutex );
680  if ( d->m_actionQueue.contains( action ) ) {
681  return;
682  }
683  d->m_actionQueue << action;
684  }
685 
686  d->processQueue();
687 }
688 
689 void NewstuffModel::cancel( int index )
690 {
691  if ( !d->isTransitioning( index ) ) {
692  return;
693  }
694 
695  { // <-- do not remove, mutex locker scope
696  QMutexLocker locker( &d->m_mutex );
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;
703  }
704 
705  if ( d->m_unpackProcess ) {
706  d->m_unpackProcess->terminate();
707  d->m_unpackProcess->deleteLater();
708  d->m_unpackProcess = 0;
709  }
710 
711  if ( d->m_currentFile ) {
712  d->m_currentFile->deleteLater();
713  d->m_currentFile = 0;
714  }
715 
716  d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
717 
718  emit installationFailed( d->m_currentAction.first, tr( "Installation aborted by user." ) );
719  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
720  } else {
721  // Shall we interrupt this?
722  }
723  } else {
724  if ( d->m_currentAction.second == NewstuffModelPrivate::Install ) {
725  NewstuffModelPrivate::Action install( index, NewstuffModelPrivate::Install );
726  d->m_actionQueue.removeAll( install );
727  emit installationFailed( index, tr( "Installation aborted by user." ) );
728  } else {
729  NewstuffModelPrivate::Action uninstall( index, NewstuffModelPrivate::Uninstall );
730  d->m_actionQueue.removeAll( uninstall );
731  emit uninstallationFinished( index ); // do we need failed here?
732  }
733  }
734  }
735 
736  d->processQueue();
737 }
738 
739 void NewstuffModel::updateProgress( qint64 bytesReceived, qint64 bytesTotal )
740 {
741  qreal const progress = qBound<qreal>( 0.0, 0.9 * bytesReceived / qreal( bytesTotal ), 1.0 );
742  emit installationProgressed( d->m_currentAction.first, progress );
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 ) {
746  // Only consider download progress of 1% and more as a data change
747  item.m_downloadedSize = bytesReceived;
748  QModelIndex const affected = index( d->m_currentAction.first );
749  emit dataChanged( affected, affected );
750  }
751 }
752 
753 void NewstuffModel::retrieveData()
754 {
755  if ( d->m_currentReply && d->m_currentReply->isReadable() ) {
756  // check if we are redirected
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)) );
764  } else {
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();
770  d->installMap();
771  }
772  }
773  }
774 }
775 
776 void NewstuffModel::mapInstalled( int exitStatus )
777 {
778  if ( d->m_unpackProcess ) {
779  d->m_unpackProcess->deleteLater();
780  d->m_unpackProcess = 0;
781  }
782 
783  if ( d->m_currentFile ) {
784  d->m_currentFile->deleteLater();
785  d->m_currentFile = 0;
786  }
787 
788  emit installationProgressed( d->m_currentAction.first, 1.0 );
789  d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
790  if ( exitStatus == 0 ) {
791  emit installationFinished( d->m_currentAction.first );
792  } else {
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 ) );
795  }
796  QModelIndex const affected = index( d->m_currentAction.first );
797 
798  { // <-- do not remove, mutex locker scope
799  QMutexLocker locker( &d->m_mutex );
800  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
801  }
802  emit dataChanged( affected, affected );
803  d->processQueue();
804 }
805 
806 void NewstuffModel::mapUninstalled()
807 {
808  QModelIndex const affected = index( d->m_currentAction.first );
809  emit uninstallationFinished( d->m_currentAction.first );
810 
811  { // <-- do not remove, mutex locker scope
812  QMutexLocker locker( &d->m_mutex );
813  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
814  }
815  emit dataChanged( affected, affected );
816  d->processQueue();
817 }
818 
819 void NewstuffModel::contentsListed( int exitStatus )
820 {
821  emit installationProgressed( d->m_currentAction.first, 0.92 );
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;
827  if ( node.isNull() ) {
828  node = d->m_root.appendChild( d->m_registryDocument.createElement( "stuff" ) );
829  }
830 
831  node.toElement().setAttribute( "category", d->m_items[d->m_currentAction.first].m_category );
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;
848 
849  bool hasChildren = true;
850  while ( hasChildren ) {
852  QDomNodeList fileList = node.toElement().elementsByTagName( "installedfile" );
853  hasChildren = !fileList.isEmpty();
854  for ( int i=0; i<fileList.count(); ++i ) {
855  node.removeChild( fileList.at( i ) );
856  }
857  }
858 
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 ) );
863  }
864 
865  d->saveRegistry();
866  }
867 
868  QObject::disconnect( d->m_unpackProcess, SIGNAL(finished(int)),
869  this, SLOT(contentsListed(int)) );
870  QObject::connect( d->m_unpackProcess, SIGNAL(finished(int)),
871  this, SLOT(mapInstalled(int)) );
872  QStringList arguments = QStringList() << "-x" << "-z" << "-f" << d->m_currentFile->fileName();
873  d->m_unpackProcess->start( "tar", arguments );
874  } else {
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 ) );
877 
878  { // <-- do not remove, mutex locker scope
879  QMutexLocker locker( &d->m_mutex );
880  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
881  }
882  d->processQueue();
883  }
884 }
885 
886 void NewstuffModelPrivate::processQueue()
887 {
888  if ( m_actionQueue.empty() || m_currentAction.first >= 0 ) {
889  return;
890  }
891 
892  { // <-- do not remove, mutex locker scope
893  QMutexLocker locker( &m_mutex );
894  m_currentAction = m_actionQueue.takeFirst();
895  }
896  if ( m_currentAction.second == Install ) {
897  if ( !m_currentFile ) {
898  QFileInfo const file = m_items.at( m_currentAction.first ).m_payloadUrl.path();
899  m_currentFile = new QTemporaryFile( QDir::tempPath() + "/marble-XXXXXX-" + file.fileName() );
900  }
901 
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()) );
907  QObject::connect( m_currentReply, SIGNAL(downloadProgress(qint64,qint64)),
908  m_parent, SLOT(updateProgress(qint64,qint64)) );
910  } else {
911  mDebug() << "Failed to write to " << m_currentFile->fileName();
912  }
913  } else {
914  // Run in a separate thread to keep the ui responsive
915  QFutureWatcher<void>* watcher = new QFutureWatcher<void>( m_parent );
916  QObject::connect( watcher, SIGNAL(finished()), m_parent, SLOT(mapUninstalled()) );
917  QObject::connect( watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()) );
918 
919  QFuture<void> future = QtConcurrent::run( this, &NewstuffModelPrivate::uninstall, m_currentAction.first );
920  watcher->setFuture( future );
921  }
922 }
923 
924 NewstuffItem NewstuffModelPrivate::importNode(const QDomNode &node)
925 {
926  NewstuffItem item;
927  item.m_category = node.attributes().namedItem( "category" ).toAttr().value();
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 );
936  return item;
937 }
938 
939 bool NewstuffModelPrivate::isTransitioning( int index ) const
940 {
941  if ( m_currentAction.first == index ) {
942  return true;
943  }
944 
945  foreach( const Action &action, m_actionQueue ) {
946  if ( action.first == index ) {
947  return true;
948  }
949  }
950 
951  return false;
952 }
953 
954 }
955 
956 #include "NewstuffModel.moc"
QVariant::toUrl
QUrl toUrl() const
Marble::NewstuffModel::IsTransitioning
Definition: NewstuffModel.h:51
QModelIndex
QDomElement::elementsByTagName
QDomNodeList elementsByTagName(const QString &tagname) const
QMutex
Marble::NewstuffModel::installationProgressed
void installationProgressed(int newstuffindex, qreal progress)
QDomNodeList::item
QDomNode item(int index) const
Marble::NewstuffModel::targetDirectoryChanged
void targetDirectoryChanged()
QImage::fromData
QImage fromData(const uchar *data, int size, const char *format)
QAbstractItemModel::setRoleNames
void setRoleNames(const QHash< int, QByteArray > &roleNames)
QDomNamedNodeMap::contains
bool contains(const QString &name) const
QDomNode::appendChild
QDomNode appendChild(const QDomNode &newChild)
QByteArray
QFile::remove
bool remove()
QString::split
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
Marble::NewstuffModel::IsInstalled
Definition: NewstuffModel.h:48
QNetworkReply
QDomNodeList
QAbstractItemModel::roleNames
const QHash< int, QByteArray > & roleNames() const
Marble::NewstuffModel::setProvider
void setProvider(const QString &downloadUrl)
Add a newstuff provider.
Definition: NewstuffModel.cpp:569
QMap< QNetworkReply *, FetchPreviewJob * >
Marble::NewstuffModel::InstalledReleaseDate
Definition: NewstuffModel.h:46
QVariant::value
T value() const
QPixmap::fromImage
QPixmap fromImage(const QImage &image, QFlags< Qt::ImageConversionFlag > flags)
Marble::NewstuffModel::data
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Overload of QAbstractListModel.
Definition: NewstuffModel.cpp:518
Marble::MarbleDirs::localPath
static QString localPath()
Definition: MarbleDirs.cpp:223
QDomDocument::documentElement
QDomElement documentElement() const
Marble::NewstuffModel::installationFinished
void installationFinished(int newstuffindex)
QDomNode
QImage::isNull
bool isNull() const
NewstuffModel.h
QObject::disconnect
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QDir::root
QDir root()
QUrl::isEmpty
bool isEmpty() const
QDir::homePath
QString homePath()
MarbleDebug.h
QObject::tr
QString tr(const char *sourceText, const char *disambiguation, int n)
QFile
QProcessEnvironment::value
QString value(const QString &name, const QString &defaultValue) const
QTextStream
Marble::NewstuffModel::NewstuffModel
NewstuffModel(QObject *parent=0)
Constructor.
Definition: NewstuffModel.cpp:469
Marble::NewstuffModel::InstalledFiles
Definition: NewstuffModel.h:47
QIODevice::at
Offset at() const
QDomNode::toElement
QDomElement toElement() const
Marble::NewstuffModel::ReleaseDate
Definition: NewstuffModel.h:42
QDomNodeList::isEmpty
bool isEmpty() const
Marble::NewstuffModel::PayloadSize
Definition: NewstuffModel.h:52
QModelIndex::isValid
bool isValid() const
QNetworkRequest
QDomNodeList::count
int count() const
QDir::rmdir
bool rmdir(const QString &dirName) const
QDomElement::text
QString text() const
QVariant::isNull
bool isNull() const
QDir::tempPath
QString tempPath()
QAbstractItemModel::dataChanged
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
Marble::NewstuffModel::setTargetDirectory
void setTargetDirectory(const QString &targetDirectory)
Definition: NewstuffModel.cpp:585
QHash
Marble::NewstuffModel::License
Definition: NewstuffModel.h:39
QFileInfo::fileName
QString fileName() const
QProcess
QObject
QDomElement::setAttribute
void setAttribute(const QString &name, const QString &value)
QAbstractListModel::index
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const
QAbstractListModel
Marble::NewstuffModel::installationFailed
void installationFailed(int newstuffindex, const QString &error)
MarbleDirs.h
QFileInfo::absoluteFilePath
QString absoluteFilePath() const
QModelIndex::row
int row() const
QString::startsWith
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QIODevice::readAll
QByteArray readAll()
QString::endsWith
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QDomProcessingInstruction
QtConcurrent::run
QFuture< T > run(Function function,...)
Marble::NewstuffModel::providerChanged
void providerChanged()
Marble::NewstuffModel::install
void install(int index)
Definition: NewstuffModel.cpp:649
Marble::NewstuffModel::uninstall
void uninstall(int index)
Definition: NewstuffModel.cpp:667
QString
QList
Marble::NewstuffModel::Preview
Definition: NewstuffModel.h:43
Marble::NewstuffModel::Author
Definition: NewstuffModel.h:38
QFile::open
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QFutureWatcher::setFuture
void setFuture(const QFuture< T > &future)
QStringList
QPair< int, UserAction >
QPixmap
QDomDocument::createTextNode
QDomText createTextNode(const QString &value)
QFileInfo
QDomNamedNodeMap::namedItem
QDomNode namedItem(const QString &name) const
QList::end
iterator end()
QFileInfo::exists
bool exists() const
Marble::NewstuffModel::provider
QString provider() const
QDomNode::removeChild
QDomNode removeChild(const QDomNode &oldChild)
QDomNode::namedItem
QDomNode namedItem(const QString &name) const
QUrl
QLatin1Char
QDomDocument
Marble::NewstuffModel::Summary
Definition: NewstuffModel.h:40
QFile::close
virtual void close()
QDomAttr::value
QString value() const
QImage
Marble::NewstuffModel::Category
Definition: NewstuffModel.h:50
QDomNode::isNull
bool isNull() const
QDir
QNetworkReply::header
QVariant header(QNetworkRequest::KnownHeaders header) const
Marble::NewstuffModel::Name
Definition: NewstuffModel.h:37
QNetworkAccessManager
Marble::NewstuffModel::PayloadTag
Definition: NewstuffModel.h:57
QNetworkReply::operation
QNetworkAccessManager::Operation operation() const
QDomNode::firstChild
QDomNode firstChild() const
QString::mid
QString mid(int position, int n) const
QVector< NewstuffItem >
QLatin1String
Marble::NewstuffModel::cancel
void cancel(int index)
Definition: NewstuffModel.cpp:689
QAbstractItemModel::hasChildren
virtual bool hasChildren(const QModelIndex &parent) const
QMutexLocker
Marble::NewstuffModel::InstalledVersion
Definition: NewstuffModel.h:45
QString::at
const QChar at(int position) const
Marble::NewstuffModel::Payload
Definition: NewstuffModel.h:44
QDomNode::toAttr
QDomAttr toAttr() const
QNetworkReply::attribute
QVariant attribute(QNetworkRequest::Attribute code) const
Marble::NewstuffModel::setRegistryFile
void setRegistryFile(const QString &registryFile, IdTag idTag=PayloadTag)
Definition: NewstuffModel.cpp:605
QNetworkReply::url
QUrl url() const
QString::length
int length() const
QFutureWatcher
Marble::NewstuffModel::count
int count() const
QVariant::isValid
bool isValid() const
Marble::NewstuffModel::Version
Definition: NewstuffModel.h:41
Marble::NewstuffModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const
Overload of QAbstractListModel.
Definition: NewstuffModel.cpp:509
QProcessEnvironment::systemEnvironment
QProcessEnvironment systemEnvironment()
QTemporaryFile
QDomNodeList::size
int size() const
QDomNodeList::length
uint length() const
QDomDocument::createElement
QDomElement createElement(const QString &tagName)
Marble::NewstuffModel::~NewstuffModel
~NewstuffModel()
Destructor.
Definition: NewstuffModel.cpp:504
Marble::License
The class that displays copyright info.
Definition: License.h:33
QFileInfo::absolutePath
QString absolutePath() const
Marble::NewstuffModel::IsUpgradable
Definition: NewstuffModel.h:49
QFuture
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QDomElement
Marble::mDebug
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:36
Marble::NewstuffModel::DownloadedSize
Definition: NewstuffModel.h:53
QList::begin
iterator begin()
QDomNode::attributes
QDomNamedNodeMap attributes() const
Marble::NewstuffModel::targetDirectory
QString targetDirectory() const
Marble::NewstuffModel::registryFile
QString registryFile() const
Marble::NewstuffModel::IdTag
IdTag
Definition: NewstuffModel.h:56
QDir::mkpath
bool mkpath(const QString &dirPath) const
QIcon
Marble::NewstuffModel::registryFileChanged
void registryFileChanged()
QDomDocument::setContent
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QDomNodeList::at
QDomNode at(int index) const
Marble::NewstuffModel::NameTag
Definition: NewstuffModel.h:58
QVariant
Marble::NewstuffModel::uninstallationFinished
void uninstallationFinished(int newstuffindex)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:13:41 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

marble

Skip menu "marble"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdeedu API Reference

Skip menu "kdeedu API Reference"
  • Analitza
  •     lib
  • kalgebra
  • kalzium
  •   libscience
  • kanagram
  • kig
  •   lib
  • klettres
  • marble
  • parley
  • rocs
  •   App
  •   RocsCore
  •   VisualEditor
  •   stepcore

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal