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

marble

  • sources
  • kde-4.12
  • 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  bool canExecute( const QString &executable ) const;
122 
123  void installMap();
124 
125  void updateModel();
126 
127  void saveRegistry();
128 
129  void uninstall( int index );
130 
131  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  NewstuffItem importNode( const QDomNode &node ) const;
138 
139  bool isTransitioning( int index ) const;
140 
141  template<class T>
142  void readValue( const QDomNode &node, const QString &key, T* target ) const;
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  for ( unsigned int i = 0; i < items.length(); ++i ) {
312  m_items << importNode( items.item( i ) );
313  }
314 
315  updateModel();
316 }
317 
318 bool NewstuffModelPrivate::canExecute( const QString &executable ) const
319 {
320  QString path = QProcessEnvironment::systemEnvironment().value( "PATH", "/usr/local/bin:/usr/bin:/bin" );
321  foreach( const QString &dir, path.split( QLatin1Char( ':' ) ) ) {
322  QFileInfo application( QDir( dir ), executable );
323  if ( application.exists() ) {
324  return true;
325  }
326  }
327 
328  return false;
329 }
330 
331 void NewstuffModelPrivate::installMap()
332 {
333  if ( m_unpackProcess ) {
334  m_unpackProcess->close();
335  delete m_unpackProcess;
336  m_unpackProcess = 0;
337  } else if ( m_currentFile->fileName().endsWith( QLatin1String( "tar.gz" ) ) && canExecute( "tar" ) ) {
338  m_unpackProcess = new QProcess;
339  QObject::connect( m_unpackProcess, SIGNAL(finished(int)),
340  m_parent, SLOT(contentsListed(int)) );
341  QStringList arguments = QStringList() << "-t" << "-z" << "-f" << m_currentFile->fileName();
342  m_unpackProcess->setWorkingDirectory( m_targetDirectory );
343  m_unpackProcess->start( "tar", arguments );
344  } else {
345  if ( !m_currentFile->fileName().endsWith( QLatin1String( "tar.gz" ) ) ) {
346  mDebug() << "Can only handle tar.gz files";
347  } else {
348  mDebug() << "Cannot extract archive: tar executable not found in PATH.";
349  }
350  }
351 }
352 
353 void NewstuffModelPrivate::updateModel()
354 {
355  QDomNodeList items = m_root.elementsByTagName( "stuff" );
356  for ( unsigned int i = 0; i < items.length(); ++i ) {
357  QString const key = m_idTag == NewstuffModel::PayloadTag ? "payload" : "name";
358  QDomNodeList matches = items.item( i ).toElement().elementsByTagName( key );
359  if ( matches.size() == 1 ) {
360  QString const value = matches.at( 0 ).toElement().text();
361  bool found = false;
362  for ( int j=0; j<m_items.size() && !found; ++j ) {
363  NewstuffItem &item = m_items[j];
364  if ( m_idTag == NewstuffModel::PayloadTag && item.m_payloadUrl == value ) {
365  item.m_registryNode = items.item( i );
366  found = true;
367  }
368  if ( m_idTag == NewstuffModel::NameTag && item.m_name == value ) {
369  item.m_registryNode = items.item( i );
370  found = true;
371  }
372  }
373 
374  if ( !found ) {
375  // Not found in newstuff or newstuff not there yet
376  NewstuffItem item = importNode( items.item( i ) );
377  if ( m_idTag == NewstuffModel::PayloadTag ) {
378  item.m_registryNode = items.item( i );
379  } else if ( m_idTag == NewstuffModel::NameTag ) {
380  item.m_registryNode = items.item( i );
381  }
382  m_items << item;
383  }
384  }
385  }
386 
387  m_parent->beginResetModel();
388  m_parent->endResetModel();
389 }
390 
391 void NewstuffModelPrivate::saveRegistry()
392 {
393  QFile output( m_registryFile );
394  if ( !output.open( QFile::WriteOnly ) ) {
395  mDebug() << "Cannot open " << m_registryFile << " for writing";
396  } else {
397  QTextStream outStream( &output );
398  outStream << m_registryDocument.toString( 2 );
399  outStream.flush();
400  output.close();
401  }
402 }
403 
404 void NewstuffModelPrivate::uninstall( int index )
405 {
406  // Delete all files first, then directories (deeper ones first)
407 
408  QStringList directories;
409  QStringList const files = m_items[index].installedFiles();
410  foreach( const QString &file, files ) {
411  if ( file.endsWith( '/' ) ) {
412  directories << file;
413  } else {
414  QFile::remove( file );
415  }
416  }
417 
418  qSort( directories.begin(), directories.end(), NewstuffItem::deeperThan );
419  foreach( const QString &dir, directories ) {
420  QDir::root().rmdir( dir );
421  }
422 
423  m_items[index].m_registryNode.parentNode().removeChild( m_items[index].m_registryNode );
424  m_items[index].m_registryNode.clear();
425  saveRegistry();
426 }
427 
428 void NewstuffModelPrivate::changeNode( QDomNode &node, QDomDocument &domDocument, const QString &key, const QString &value, NodeAction action )
429 {
430  if ( action == Append ) {
431  QDomNode newNode = node.appendChild( domDocument.createElement( key ) );
432  newNode.appendChild( domDocument.createTextNode( value ) );
433  } else {
434  QDomNode oldNode = node.namedItem( key );
435  if ( !oldNode.isNull() ) {
436  oldNode.removeChild( oldNode.firstChild() );
437  oldNode.appendChild( domDocument.createTextNode( value ) );
438  }
439  }
440 }
441 
442 template<class T>
443 void NewstuffModelPrivate::readValue( const QDomNode &node, const QString &key, T* target ) const
444 {
445  QDomNodeList matches = node.toElement().elementsByTagName( key );
446  if ( matches.size() == 1 ) {
447  *target = matches.at( 0 ).toElement().text();
448  } else {
449  for ( int i=0; i<matches.size(); ++i ) {
450  if ( matches.at( i ).attributes().contains( "lang" ) &&
451  matches.at( i ).attributes().namedItem( "lang").toAttr().value() == "en" ) {
452  *target = matches.at( i ).toElement().text();
453  return;
454  }
455  }
456  }
457 }
458 
459 NewstuffModel::NewstuffModel( QObject *parent ) :
460  QAbstractListModel( parent ), d( new NewstuffModelPrivate( this ) )
461 {
462  setTargetDirectory( MarbleDirs::localPath() + "/maps" );
463  // no default registry file
464 
465  connect( &d->m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
466  this, SLOT(handleProviderData(QNetworkReply*)) );
467 
468  QHash<int,QByteArray> roles;
469  roles[Qt::DisplayRole] = "display";
470  roles[Name] = "name";
471  roles[Author] = "author";
472  roles[License] = "license";
473  roles[Summary] = "summary";
474  roles[Version] = "version";
475  roles[ReleaseDate] = "releasedate";
476  roles[Preview] = "preview";
477  roles[Payload] = "payload";
478  roles[InstalledVersion] = "installedversion";
479  roles[InstalledReleaseDate] = "installedreleasedate";
480  roles[InstalledFiles] = "installedfiles";
481  roles[IsInstalled] = "installed";
482  roles[IsUpgradable] = "upgradable";
483  roles[Category] = "category";
484  roles[IsTransitioning] = "transitioning";
485  roles[PayloadSize] = "size";
486  roles[DownloadedSize] = "downloaded";
487 #if QT_VERSION < 0x050000
488  setRoleNames( roles );
489 #else
490  d->m_roleNames = roles;
491 #endif
492 }
493 
494 NewstuffModel::~NewstuffModel()
495 {
496  delete d;
497 }
498 
499 int NewstuffModel::rowCount ( const QModelIndex &parent ) const
500 {
501  if ( !parent.isValid() ) {
502  return d->m_items.size();
503  }
504 
505  return 0;
506 }
507 
508 QVariant NewstuffModel::data ( const QModelIndex &index, int role ) const
509 {
510  if ( index.isValid() && index.row() >= 0 && index.row() < d->m_items.size() ) {
511  switch ( role ) {
512  case Qt::DisplayRole: return d->m_items.at( index.row() ).m_name;
513  case Qt::DecorationRole: return d->preview( index.row() );
514  case Name: return d->m_items.at( index.row() ).m_name;
515  case Author: return d->m_items.at( index.row() ).m_author;
516  case License: return d->m_items.at( index.row() ).m_license;
517  case Summary: return d->m_items.at( index.row() ).m_summary;
518  case Version: return d->m_items.at( index.row() ).m_version;
519  case ReleaseDate: return d->m_items.at( index.row() ).m_releaseDate;
520  case Preview: return d->m_items.at( index.row() ).m_previewUrl;
521  case Payload: return d->m_items.at( index.row() ).m_payloadUrl;
522  case InstalledVersion: return d->m_items.at( index.row() ).installedVersion();
523  case InstalledReleaseDate: return d->m_items.at( index.row() ).installedReleaseDate();
524  case InstalledFiles: return d->m_items.at( index.row() ).installedFiles();
525  case IsInstalled: return !d->m_items.at( index.row() ).m_registryNode.isNull();
526  case IsUpgradable: return d->m_items.at( index.row() ).isUpgradable();
527  case Category: return d->m_items.at( index.row() ).m_category;
528  case IsTransitioning: return d->isTransitioning( index.row() );
529  case PayloadSize: {
530  qint64 const size = d->m_items.at( index.row() ).m_payloadSize;
531  QUrl const url = d->m_items.at( index.row() ).m_payloadUrl;
532  if ( size < -1 && !url.isEmpty() ) {
533  d->m_items[index.row()].m_payloadSize = -1; // prevent several head requests for the same item
534  d->m_networkAccessManager.head( QNetworkRequest( url ) );
535  }
536 
537  return qMax<qint64>( -1, size );
538  }
539  case DownloadedSize: return d->m_items.at( index.row() ).m_downloadedSize;
540  }
541  }
542 
543  return QVariant();
544 }
545 
546 #if QT_VERSION >= 0x050000
547 QHash<int, QByteArray> NewstuffModel::roleNames() const
548 {
549  return d->m_roleNames;
550 }
551 #endif
552 
553 
554 int NewstuffModel::count()
555 {
556  return rowCount();
557 }
558 
559 void NewstuffModel::setProvider( const QString &downloadUrl )
560 {
561  if ( downloadUrl == d->m_provider ) {
562  return;
563  }
564 
565  d->m_provider = downloadUrl;
566  emit providerChanged();
567  d->m_networkAccessManager.get( QNetworkRequest( QUrl( downloadUrl ) ) );
568 }
569 
570 QString NewstuffModel::provider() const
571 {
572  return d->m_provider;
573 }
574 
575 void NewstuffModel::setTargetDirectory( const QString &targetDirectory )
576 {
577  if ( targetDirectory != d->m_targetDirectory ) {
578  QFileInfo targetDir( targetDirectory );
579  if ( !targetDir.exists() ) {
580  if ( !QDir::root().mkpath( targetDir.absoluteFilePath() ) ) {
581  qDebug() << "Failed to create directory " << targetDirectory << ", newstuff installation might fail.";
582  }
583  }
584 
585  d->m_targetDirectory = targetDirectory;
586  emit targetDirectoryChanged();
587  }
588 }
589 
590 QString NewstuffModel::targetDirectory() const
591 {
592  return d->m_targetDirectory;
593 }
594 
595 void NewstuffModel::setRegistryFile( const QString &filename, IdTag idTag )
596 {
597  QString registryFile = filename;
598  if ( registryFile.startsWith( '~' ) && registryFile.length() > 1 ) {
599  registryFile = QDir::homePath() + registryFile.mid( 1 );
600  }
601 
602  if ( d->m_registryFile != registryFile ) {
603  d->m_registryFile = registryFile;
604  d->m_idTag = idTag;
605  emit registryFileChanged();
606 
607  QFileInfo inputFile( registryFile );
608  if ( !inputFile.exists() ) {
609  QDir::root().mkpath( inputFile.absolutePath() );
610  d->m_registryDocument = QDomDocument( "khotnewstuff3" );
611  QDomProcessingInstruction header = d->m_registryDocument.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"utf-8\"" );
612  d->m_registryDocument.appendChild( header );
613  d->m_root = d->m_registryDocument.createElement( "hotnewstuffregistry" );
614  d->m_registryDocument.appendChild( d->m_root );
615  } else {
616  QFile input( registryFile );
617  if ( !input.open( QFile::ReadOnly ) ) {
618  mDebug() << "Cannot open newstuff registry " << registryFile;
619  return;
620  }
621 
622  if ( !d->m_registryDocument.setContent( &input ) ) {
623  mDebug() << "Cannot parse newstuff registry " << registryFile;
624  return;
625  }
626  input.close();
627  d->m_root = d->m_registryDocument.documentElement();
628  }
629 
630  d->updateModel();
631  }
632 }
633 
634 QString NewstuffModel::registryFile() const
635 {
636  return d->m_registryFile;
637 }
638 
639 void NewstuffModel::install( int index )
640 {
641  if ( index < 0 || index >= d->m_items.size() ) {
642  return;
643  }
644 
645  NewstuffModelPrivate::Action action( index, NewstuffModelPrivate::Install );
646  { // <-- do not remove, mutex locker scope
647  QMutexLocker locker( &d->m_mutex );
648  if ( d->m_actionQueue.contains( action ) ) {
649  return;
650  }
651  d->m_actionQueue << action;
652  }
653 
654  d->processQueue();
655 }
656 
657 void NewstuffModel::uninstall( int idx )
658 {
659  if ( idx < 0 || idx >= d->m_items.size() ) {
660  return;
661  }
662 
663  if ( d->m_items[idx].m_registryNode.isNull() ) {
664  emit uninstallationFinished( idx );
665  }
666 
667  NewstuffModelPrivate::Action action( idx, NewstuffModelPrivate::Uninstall );
668  { // <-- do not remove, mutex locker scope
669  QMutexLocker locker( &d->m_mutex );
670  if ( d->m_actionQueue.contains( action ) ) {
671  return;
672  }
673  d->m_actionQueue << action;
674  }
675 
676  d->processQueue();
677 }
678 
679 void NewstuffModel::cancel( int index )
680 {
681  if ( !d->isTransitioning( index ) ) {
682  return;
683  }
684 
685  { // <-- do not remove, mutex locker scope
686  QMutexLocker locker( &d->m_mutex );
687  if ( d->m_currentAction.first == index ) {
688  if ( d->m_currentAction.second == NewstuffModelPrivate::Install ) {
689  if ( d->m_currentReply ) {
690  d->m_currentReply->abort();
691  d->m_currentReply->deleteLater();
692  d->m_currentReply = 0;
693  }
694 
695  if ( d->m_unpackProcess ) {
696  d->m_unpackProcess->terminate();
697  d->m_unpackProcess->deleteLater();
698  d->m_unpackProcess = 0;
699  }
700 
701  if ( d->m_currentFile ) {
702  d->m_currentFile->deleteLater();
703  d->m_currentFile = 0;
704  }
705 
706  d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
707 
708  emit installationFailed( d->m_currentAction.first, tr( "Installation aborted by user." ) );
709  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
710  } else {
711  // Shall we interrupt this?
712  }
713  } else {
714  if ( d->m_currentAction.second == NewstuffModelPrivate::Install ) {
715  NewstuffModelPrivate::Action install( index, NewstuffModelPrivate::Install );
716  d->m_actionQueue.removeAll( install );
717  emit installationFailed( index, tr( "Installation aborted by user." ) );
718  } else {
719  NewstuffModelPrivate::Action uninstall( index, NewstuffModelPrivate::Uninstall );
720  d->m_actionQueue.removeAll( uninstall );
721  emit uninstallationFinished( index ); // do we need failed here?
722  }
723  }
724  }
725 
726  d->processQueue();
727 }
728 
729 void NewstuffModel::updateProgress( qint64 bytesReceived, qint64 bytesTotal )
730 {
731  qreal const progress = qBound<qreal>( 0.0, 0.9 * bytesReceived / qreal( bytesTotal ), 1.0 );
732  emit installationProgressed( d->m_currentAction.first, progress );
733  NewstuffItem &item = d->m_items[d->m_currentAction.first];
734  item.m_payloadSize = bytesTotal;
735  if ( qreal(bytesReceived-item.m_downloadedSize)/bytesTotal >= 0.01 || progress >= 0.9 ) {
736  // Only consider download progress of 1% and more as a data change
737  item.m_downloadedSize = bytesReceived;
738  QModelIndex const affected = index( d->m_currentAction.first );
739  emit dataChanged( affected, affected );
740  }
741 }
742 
743 void NewstuffModel::retrieveData()
744 {
745  if ( d->m_currentReply && d->m_currentReply->isReadable() ) {
746  // check if we are redirected
747  const QVariant redirectionAttribute = d->m_currentReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
748  if ( !redirectionAttribute.isNull() ) {
749  d->m_currentReply = d->m_networkAccessManager.get( QNetworkRequest( redirectionAttribute.toUrl() ) );
750  QObject::connect( d->m_currentReply, SIGNAL(readyRead()), this, SLOT(retrieveData()) );
751  QObject::connect( d->m_currentReply, SIGNAL(readChannelFinished()), this, SLOT(retrieveData()) );
752  QObject::connect( d->m_currentReply, SIGNAL(downloadProgress(qint64,qint64)),
753  this, SLOT(updateProgress(qint64,qint64)) );
754  } else {
755  d->m_currentFile->write( d->m_currentReply->readAll() );
756  if ( d->m_currentReply->isFinished() ) {
757  d->m_currentReply->deleteLater();
758  d->m_currentReply = 0;
759  d->m_currentFile->flush();
760  d->installMap();
761  }
762  }
763  }
764 }
765 
766 void NewstuffModel::mapInstalled( int exitStatus )
767 {
768  if ( d->m_unpackProcess ) {
769  d->m_unpackProcess->deleteLater();
770  d->m_unpackProcess = 0;
771  }
772 
773  if ( d->m_currentFile ) {
774  d->m_currentFile->deleteLater();
775  d->m_currentFile = 0;
776  }
777 
778  emit installationProgressed( d->m_currentAction.first, 1.0 );
779  d->m_items[d->m_currentAction.first].m_downloadedSize = 0;
780  if ( exitStatus == 0 ) {
781  emit installationFinished( d->m_currentAction.first );
782  } else {
783  mDebug() << "Process exit status " << exitStatus << " indicates an error.";
784  emit installationFailed( d->m_currentAction.first , QString( "Unable to unpack file. Process exited with status code %1." ).arg( exitStatus ) );
785  }
786  QModelIndex const affected = index( d->m_currentAction.first );
787 
788  { // <-- do not remove, mutex locker scope
789  QMutexLocker locker( &d->m_mutex );
790  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
791  }
792  emit dataChanged( affected, affected );
793  d->processQueue();
794 }
795 
796 void NewstuffModel::mapUninstalled()
797 {
798  QModelIndex const affected = index( d->m_currentAction.first );
799  emit uninstallationFinished( d->m_currentAction.first );
800 
801  { // <-- do not remove, mutex locker scope
802  QMutexLocker locker( &d->m_mutex );
803  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
804  }
805  emit dataChanged( affected, affected );
806  d->processQueue();
807 }
808 
809 void NewstuffModel::contentsListed( int exitStatus )
810 {
811  emit installationProgressed( d->m_currentAction.first, 0.92 );
812  if ( exitStatus == 0 ) {
813  if ( !d->m_registryFile.isEmpty() ) {
814  NewstuffItem &item = d->m_items[d->m_currentAction.first];
815  QDomNode node = item.m_registryNode;
816  NewstuffModelPrivate::NodeAction action = node.isNull() ? NewstuffModelPrivate::Append : NewstuffModelPrivate::Replace;
817  if ( node.isNull() ) {
818  node = d->m_root.appendChild( d->m_registryDocument.createElement( "stuff" ) );
819  }
820 
821  node.toElement().setAttribute( "category", d->m_items[d->m_currentAction.first].m_category );
822  d->changeNode( node, d->m_registryDocument, "name", item.m_name, action );
823  d->changeNode( node, d->m_registryDocument, "providerid", d->m_provider, action );
824  d->changeNode( node, d->m_registryDocument, "author", item.m_author, action );
825  d->changeNode( node, d->m_registryDocument, "homepage", QString(), action );
826  d->changeNode( node, d->m_registryDocument, "licence", item.m_license, action );
827  d->changeNode( node, d->m_registryDocument, "version", item.m_version, action );
828  QString const itemId = d->m_idTag == PayloadTag ? item.m_payloadUrl.toString() : item.m_name;
829  d->changeNode( node, d->m_registryDocument, "id", itemId, action );
830  d->changeNode( node, d->m_registryDocument, "releasedate", item.m_releaseDate, action );
831  d->changeNode( node, d->m_registryDocument, "summary", item.m_summary, action );
832  d->changeNode( node, d->m_registryDocument, "changelog", QString(), action );
833  d->changeNode( node, d->m_registryDocument, "preview", item.m_previewUrl.toString(), action );
834  d->changeNode( node, d->m_registryDocument, "previewBig", item.m_previewUrl.toString(), action );
835  d->changeNode( node, d->m_registryDocument, "payload", item.m_payloadUrl.toString(), action );
836  d->changeNode( node, d->m_registryDocument, "status", "installed", action );
837  d->m_items[d->m_currentAction.first].m_registryNode = node;
838 
839  bool hasChildren = true;
840  while ( hasChildren ) {
842  QDomNodeList fileList = node.toElement().elementsByTagName( "installedfile" );
843  hasChildren = !fileList.isEmpty();
844  for ( int i=0; i<fileList.count(); ++i ) {
845  node.removeChild( fileList.at( i ) );
846  }
847  }
848 
849  QStringList const files = QString( d->m_unpackProcess->readAllStandardOutput() ).split( '\n', QString::SkipEmptyParts );
850  foreach( const QString &file, files ) {
851  QDomNode fileNode = node.appendChild( d->m_registryDocument.createElement( "installedfile" ) );
852  fileNode.appendChild( d->m_registryDocument.createTextNode( d->m_targetDirectory + '/' + file ) );
853  }
854 
855  d->saveRegistry();
856  }
857 
858  QObject::disconnect( d->m_unpackProcess, SIGNAL(finished(int)),
859  this, SLOT(contentsListed(int)) );
860  QObject::connect( d->m_unpackProcess, SIGNAL(finished(int)),
861  this, SLOT(mapInstalled(int)) );
862  QStringList arguments = QStringList() << "-x" << "-z" << "-f" << d->m_currentFile->fileName();
863  d->m_unpackProcess->start( "tar", arguments );
864  } else {
865  mDebug() << "Process exit status " << exitStatus << " indicates an error.";
866  emit installationFailed( d->m_currentAction.first , QString( "Unable to list file contents. Process exited with status code %1." ).arg( exitStatus ) );
867 
868  { // <-- do not remove, mutex locker scope
869  QMutexLocker locker( &d->m_mutex );
870  d->m_currentAction = NewstuffModelPrivate::Action( -1, NewstuffModelPrivate::Install );
871  }
872  d->processQueue();
873  }
874 }
875 
876 void NewstuffModelPrivate::processQueue()
877 {
878  if ( m_actionQueue.empty() || m_currentAction.first >= 0 ) {
879  return;
880  }
881 
882  { // <-- do not remove, mutex locker scope
883  QMutexLocker locker( &m_mutex );
884  m_currentAction = m_actionQueue.takeFirst();
885  }
886  if ( m_currentAction.second == Install ) {
887  if ( !m_currentFile ) {
888  QFileInfo const file = m_items.at( m_currentAction.first ).m_payloadUrl.path();
889  m_currentFile = new QTemporaryFile( QDir::tempPath() + "/marble-XXXXXX-" + file.fileName() );
890  }
891 
892  if ( m_currentFile->open() ) {
893  QUrl const payload = m_items.at( m_currentAction.first ).m_payloadUrl;
894  m_currentReply = m_networkAccessManager.get( QNetworkRequest( payload ) );
895  QObject::connect( m_currentReply, SIGNAL(readyRead()), m_parent, SLOT(retrieveData()) );
896  QObject::connect( m_currentReply, SIGNAL(readChannelFinished()), m_parent, SLOT(retrieveData()) );
897  QObject::connect( m_currentReply, SIGNAL(downloadProgress(qint64,qint64)),
898  m_parent, SLOT(updateProgress(qint64,qint64)) );
900  } else {
901  mDebug() << "Failed to write to " << m_currentFile->fileName();
902  }
903  } else {
904  // Run in a separate thread to keep the ui responsive
905  QFutureWatcher<void>* watcher = new QFutureWatcher<void>( m_parent );
906  QObject::connect( watcher, SIGNAL(finished()), m_parent, SLOT(mapUninstalled()) );
907  QObject::connect( watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()) );
908 
909  QFuture<void> future = QtConcurrent::run( this, &NewstuffModelPrivate::uninstall, m_currentAction.first );
910  watcher->setFuture( future );
911  }
912 }
913 
914 NewstuffItem NewstuffModelPrivate::importNode(const QDomNode &node) const
915 {
916  NewstuffItem item;
917  item.m_category = node.attributes().namedItem( "category" ).toAttr().value();
918  readValue<QString>( node, "name", &item.m_name );
919  readValue<QString>( node, "author", &item.m_author );
920  readValue<QString>( node, "licence", &item.m_license );
921  readValue<QString>( node, "summary", &item.m_summary );
922  readValue<QString>( node, "version", &item.m_version );
923  readValue<QString>( node, "releasedate", &item.m_releaseDate );
924  readValue<QUrl>( node, "preview", &item.m_previewUrl );
925  readValue<QUrl>( node, "payload", &item.m_payloadUrl );
926  return item;
927 }
928 
929 bool NewstuffModelPrivate::isTransitioning( int index ) const
930 {
931  if ( m_currentAction.first == index ) {
932  return true;
933  }
934 
935  foreach( const Action &action, m_actionQueue ) {
936  if ( action.first == index ) {
937  return true;
938  }
939  }
940 
941  return false;
942 }
943 
944 }
945 
946 #include "NewstuffModel.moc"
Marble::NewstuffModel::IsTransitioning
Definition: NewstuffModel.h:51
Marble::NewstuffModel::installationProgressed
void installationProgressed(int newstuffindex, qreal progress)
Marble::NewstuffModel::count
int count()
Marble::NewstuffModel::targetDirectoryChanged
void targetDirectoryChanged()
Marble::NewstuffModel::IsInstalled
Definition: NewstuffModel.h:48
Marble::NewstuffModel::setProvider
void setProvider(const QString &downloadUrl)
Add a newstuff provider.
Definition: NewstuffModel.cpp:559
Marble::NewstuffModel::InstalledReleaseDate
Definition: NewstuffModel.h:46
Marble::NewstuffModel::data
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Overload of QAbstractListModel.
Definition: NewstuffModel.cpp:508
Marble::MarbleDirs::localPath
static QString localPath()
Definition: MarbleDirs.cpp:217
Marble::NewstuffModel::installationFinished
void installationFinished(int newstuffindex)
NewstuffModel.h
QObject
MarbleDebug.h
Marble::NewstuffModel::NewstuffModel
NewstuffModel(QObject *parent=0)
Constructor.
Definition: NewstuffModel.cpp:459
Marble::NewstuffModel::InstalledFiles
Definition: NewstuffModel.h:47
Marble::NewstuffModel::ReleaseDate
Definition: NewstuffModel.h:42
Marble::NewstuffModel::PayloadSize
Definition: NewstuffModel.h:52
Marble::NewstuffModel::setTargetDirectory
void setTargetDirectory(const QString &targetDirectory)
Definition: NewstuffModel.cpp:575
Marble::NewstuffModel::License
Definition: NewstuffModel.h:39
Marble::NewstuffModel::installationFailed
void installationFailed(int newstuffindex, const QString &error)
MarbleDirs.h
Marble::NewstuffModel::providerChanged
void providerChanged()
Marble::NewstuffModel::install
void install(int index)
Definition: NewstuffModel.cpp:639
Marble::NewstuffModel::uninstall
void uninstall(int index)
Definition: NewstuffModel.cpp:657
Marble::NewstuffModel::Preview
Definition: NewstuffModel.h:43
Marble::NewstuffModel::Author
Definition: NewstuffModel.h:38
Marble::NewstuffModel::provider
QString provider() const
Marble::NewstuffModel::Summary
Definition: NewstuffModel.h:40
Marble::NewstuffModel::Category
Definition: NewstuffModel.h:50
Marble::NewstuffModel::Name
Definition: NewstuffModel.h:37
Marble::NewstuffModel::PayloadTag
Definition: NewstuffModel.h:57
Marble::NewstuffModel::cancel
void cancel(int index)
Definition: NewstuffModel.cpp:679
Marble::NewstuffModel::InstalledVersion
Definition: NewstuffModel.h:45
Marble::NewstuffModel::Payload
Definition: NewstuffModel.h:44
Marble::NewstuffModel::setRegistryFile
void setRegistryFile(const QString &registryFile, IdTag idTag=PayloadTag)
Definition: NewstuffModel.cpp:595
Marble::NewstuffModel::Version
Definition: NewstuffModel.h:41
Marble::NewstuffModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const
Overload of QAbstractListModel.
Definition: NewstuffModel.cpp:499
Marble::NewstuffModel::~NewstuffModel
~NewstuffModel()
Destructor.
Definition: NewstuffModel.cpp:494
Marble::License
The class that displays copyright info.
Definition: License.h:33
Marble::NewstuffModel::IsUpgradable
Definition: NewstuffModel.h:49
Marble::mDebug
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
Marble::NewstuffModel::DownloadedSize
Definition: NewstuffModel.h:53
Marble::NewstuffModel::targetDirectory
QString targetDirectory() const
Marble::NewstuffModel::registryFile
QString registryFile() const
Marble::NewstuffModel::IdTag
IdTag
Definition: NewstuffModel.h:56
Marble::NewstuffModel::registryFileChanged
void registryFileChanged()
Marble::NewstuffModel::NameTag
Definition: NewstuffModel.h:58
Marble::NewstuffModel::uninstallationFinished
void uninstallationFinished(int newstuffindex)
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:38:51 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
  • kstars
  • libkdeedu
  •   keduvocdocument
  • 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