00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "archivemodel.h"
00022 #include "kerfuffle/archive.h"
00023 #include "kerfuffle/jobs.h"
00024
00025 #include <QList>
00026 #include <QPixmap>
00027 #include <QFont>
00028 #include <QMimeData>
00029 #include <QDir>
00030 #include <QtDBus/QtDBus>
00031
00032 #include <KDebug>
00033 #include <KLocale>
00034 #include <KMimeType>
00035 #include <KIconLoader>
00036 #include <KIO/NetAccess>
00037
00038 class ArchiveDirNode;
00039
00040 class ArchiveNode
00041 {
00042 public:
00043 ArchiveNode( ArchiveDirNode *parent, const ArchiveEntry & entry )
00044 : m_parent( parent ), m_row( -1 )
00045 {
00046 setEntry( entry );
00047 }
00048
00049 virtual ~ArchiveNode() {}
00050
00051 ArchiveEntry entry() const { return m_entry; }
00052 void setEntry( const ArchiveEntry & entry )
00053 {
00054 m_entry = entry;
00055 QStringList pieces = entry[ FileName ].toString().split( '/', QString::SkipEmptyParts );
00056 m_name = pieces.isEmpty()? QString() : pieces.last();
00057 }
00058
00059 ArchiveDirNode *parent() const { return m_parent; }
00060
00061 int row();
00062 QString name() const { return m_name; }
00063
00064 virtual bool isDir() const { return false; }
00065
00066 QPixmap icon()
00067 {
00068 if ( m_icon.isNull() )
00069 {
00070 KMimeType::Ptr mimeType = KMimeType::findByPath( m_entry[ FileName ].toString(), 0, true );
00071 m_icon = KIconLoader::global()->loadMimeTypeIcon( mimeType->iconName(), KIconLoader::Small );
00072 }
00073 return m_icon;
00074 }
00075
00076 protected:
00077 QPixmap m_icon;
00078
00079 private:
00080 ArchiveEntry m_entry;
00081 ArchiveDirNode *m_parent;
00082 QString m_name;
00083 int m_row;
00084 };
00085
00086 class ArchiveDirNode: public ArchiveNode
00087 {
00088 public:
00089 ArchiveDirNode( ArchiveDirNode *parent, const ArchiveEntry & entry )
00090 : ArchiveNode( parent, entry )
00091 {
00092 m_icon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::mimeType( "inode/directory" )->iconName(), KIconLoader::Small );
00093 }
00094
00095 ~ArchiveDirNode()
00096 {
00097 clear();
00098 }
00099
00100 QList<ArchiveNode*>& entries() { return m_entries; }
00101
00102 virtual bool isDir() const { return true; }
00103
00104 ArchiveNode* find( const QString & name )
00105 {
00106 foreach( ArchiveNode *node, m_entries )
00107 {
00108 if ( node && ( node->name() == name ) )
00109 {
00110 return node;
00111 }
00112 }
00113 return 0;
00114 }
00115
00116 ArchiveNode* findByPath( const QString & path )
00117 {
00118 QStringList pieces = path.split( '/' );
00119 if ( pieces.isEmpty() )
00120 {
00121 return 0;
00122 }
00123
00124 ArchiveNode *next = find( pieces[ 0 ] );
00125
00126 if ( pieces.count() == 1 )
00127 {
00128 return next;
00129 }
00130 if ( next && next->isDir() )
00131 {
00132 pieces.removeAt(0);
00133 return static_cast<ArchiveDirNode*>( next )->findByPath( pieces.join( "/" ) );
00134 }
00135 return 0;
00136 }
00137
00138 void clear()
00139 {
00140 qDeleteAll( m_entries );
00141 m_entries.clear();
00142 }
00143
00144 private:
00145 QList<ArchiveNode*> m_entries;
00146 };
00147
00148 int ArchiveNode::row()
00149 {
00150 if ( m_row != -1 ) return m_row;
00151
00152 if ( parent() )
00153 {
00154 m_row = parent()->entries().indexOf( const_cast<ArchiveNode*>( this ) );
00155 return m_row;
00156 }
00157 return 0;
00158 }
00159
00160 ArchiveModel::ArchiveModel( QObject *parent )
00161 : QAbstractItemModel( parent ), m_archive( 0 ),
00162 m_rootNode( new ArchiveDirNode( 0, ArchiveEntry() ) ),
00163 m_jobTracker(0)
00164 {
00165 }
00166
00167 ArchiveModel::~ArchiveModel()
00168 {
00169 delete m_archive;
00170 m_archive = 0;
00171
00172 delete m_rootNode;
00173 m_rootNode = 0;
00174 }
00175
00176 QVariant ArchiveModel::data( const QModelIndex &index, int role ) const
00177 {
00178 if ( index.isValid() )
00179 {
00180 ArchiveNode *node = static_cast<ArchiveNode*>( index.internalPointer() );
00181 switch ( role )
00182 {
00183 case Qt::DisplayRole:
00184 {
00185
00186 int columnId = m_showColumns.at(index.column());
00187 switch (columnId) {
00188 case FileName:
00189 return node->name();
00190 case Size:
00191 if ( node->isDir() || node->entry().contains( Link ) )
00192 {
00193 return QVariant();
00194 }
00195 else
00196 {
00197 return KIO::convertSize( node->entry()[ Size ].toULongLong() );
00198 }
00199 case CompressedSize:
00200 if ( node->isDir() || node->entry().contains( Link ) )
00201 {
00202 return QVariant();
00203 }
00204 else
00205 {
00206 return KIO::convertSize( node->entry()[ CompressedSize ].toULongLong() );
00207 }
00208 case Ratio:
00209 if ( node->isDir() || node->entry().contains( Link ) )
00210 {
00211 return QVariant();
00212 }
00213 else
00214 {
00215 qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong();
00216 qulonglong size = node->entry()[ Size ].toULongLong();
00217 return QString::number( int(100 * float(size - compressedSize) / size) ) + " %";
00218 }
00219
00220 default:
00221 return node->entry().value(columnId);
00222 }
00223 break;
00224 }
00225 case Qt::DecorationRole:
00226 if ( index.column() == 0 )
00227 {
00228 return node->icon();
00229 }
00230 return QVariant();
00231 case Qt::FontRole:
00232 {
00233 QFont f;
00234 f.setItalic(node->entry()[ IsPasswordProtected ].toBool());
00235 return f;
00236 }
00237 default:
00238 return QVariant();
00239 }
00240 }
00241 return QVariant();
00242 }
00243
00244 Qt::ItemFlags ArchiveModel::flags( const QModelIndex &index ) const
00245 {
00246 Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
00247
00248 if ( index.isValid() )
00249 {
00250 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags;
00251 }
00252
00253 return 0;
00254 }
00255
00256 QVariant ArchiveModel::headerData( int section, Qt::Orientation, int role ) const
00257 {
00258 if ( role == Qt::DisplayRole )
00259 {
00260 Q_ASSERT(section < m_showColumns.size());
00261
00262 int columnId = m_showColumns.at(section);
00263
00264 switch ( columnId )
00265 {
00266 case FileName:
00267 return i18nc( "Name of a file inside an archive", "Name" );
00268 case Size:
00269 return i18nc( "Uncompressed size of a file inside an archive", "Size" );
00270 case CompressedSize:
00271 return i18nc( "Compressed size of a file inside an archive", "Compressed" );
00272 case Ratio:
00273 return i18nc( "Compression rate of file", "Rate" );
00274 case Owner:
00275 return i18nc( "File's owner username", "Owner" );
00276 case Group:
00277 return i18nc( "File's group", "Group" );
00278 case Permissions:
00279 return i18nc( "File permissions", "Mode" );
00280 case CRC:
00281 return i18nc( "CRC hash code", "CRC" );
00282 case Method:
00283 return i18nc( "Compression method", "Method" );
00284 case Version:
00285
00286 return i18nc( "File version", "Version" );
00287 case Timestamp:
00288 return i18nc( "Timestamp", "Time" );
00289 case Comment:
00290 return i18nc( "File comment", "Comment" );
00291 default:
00292 return i18nc( "Unnamed column", "??" );
00293
00294 }
00295 }
00296 return QVariant();
00297 }
00298
00299 QModelIndex ArchiveModel::index( int row, int column, const QModelIndex &parent ) const
00300 {
00301 if ( hasIndex( row, column, parent ) )
00302 {
00303 ArchiveDirNode *parentNode = parent.isValid()? static_cast<ArchiveDirNode*>( parent.internalPointer() ) : m_rootNode;
00304
00305 Q_ASSERT( parentNode->isDir() );
00306
00307 ArchiveNode *item = parentNode->entries().value( row, 0 );
00308 if ( item )
00309 {
00310 return createIndex( row, column, item );
00311 }
00312 }
00313
00314 return QModelIndex();
00315 }
00316
00317 QModelIndex ArchiveModel::parent( const QModelIndex &index ) const
00318 {
00319 if ( index.isValid() )
00320 {
00321 ArchiveNode *item = static_cast<ArchiveNode*>( index.internalPointer() );
00322 Q_ASSERT( item );
00323 if ( item->parent() && ( item->parent() != m_rootNode ) )
00324 {
00325 return createIndex( item->parent()->row(), 0, item->parent() );
00326 }
00327 }
00328 return QModelIndex();
00329 }
00330
00331 ArchiveEntry ArchiveModel::entryForIndex( const QModelIndex &index )
00332 {
00333 if ( index.isValid() )
00334 {
00335 ArchiveNode *item = static_cast<ArchiveNode*>( index.internalPointer() );
00336 Q_ASSERT( item );
00337 return item->entry();
00338 }
00339 return ArchiveEntry();
00340 }
00341
00342 int ArchiveModel::childCount( const QModelIndex &index )
00343 {
00344 if ( index.isValid() )
00345 {
00346 ArchiveNode *item = static_cast<ArchiveNode*>( index.internalPointer() );
00347 Q_ASSERT( item );
00348 if ( item->isDir() )
00349 {
00350 return static_cast<ArchiveDirNode*>( item )->entries().count();
00351 }
00352 return 0;
00353 }
00354 return -1;
00355 }
00356
00357 int ArchiveModel::rowCount( const QModelIndex &parent ) const
00358 {
00359 if ( parent.column() <= 0 )
00360 {
00361 ArchiveNode *parentNode = parent.isValid()? static_cast<ArchiveNode*>( parent.internalPointer() ) : m_rootNode;
00362
00363 if ( parentNode && parentNode->isDir() )
00364 {
00365 return static_cast<ArchiveDirNode*>( parentNode )->entries().count();
00366 }
00367 }
00368 return 0;
00369 }
00370
00371 int ArchiveModel::columnCount( const QModelIndex &parent ) const
00372 {
00373 return m_showColumns.size();
00374 if ( parent.isValid() )
00375 {
00376 return static_cast<ArchiveNode*>( parent.internalPointer() )->entry().size();
00377 }
00378 }
00379
00380 Qt::DropActions ArchiveModel::supportedDropActions () const
00381 {
00382 return Qt::CopyAction | Qt::MoveAction;
00383 }
00384
00385 QStringList ArchiveModel::mimeTypes () const
00386 {
00387 QStringList types;
00388
00389
00390 types << QString("text/uri-list")
00391 << QString( "text/plain" )
00392 << QString( "text/x-moz-url" )
00393 << QString( "application/x-kde-urilist" );
00394
00395 types << "application/x-kde-extractdrag";
00396
00397 return types;
00398 }
00399
00400 QMimeData * ArchiveModel::mimeData ( const QModelIndexList & indexes ) const
00401 {
00402
00403 kDebug (1601) ;
00404
00405 QStringList files;
00406 bool noFallback = false;
00407
00408 QString archiveName = m_archive->fileName();
00409 QString ext = QFileInfo(archiveName).suffix().toUpper();
00410 if (ext == "TAR") {
00411 archiveName.prepend("tar:");
00412 } else if (ext == "ZIP") {
00413 archiveName.prepend("zip:");
00414 } else if (archiveName.right(6).toUpper() == "TAR.GZ") {
00415 archiveName.prepend("tar:");
00416 } else
00417 noFallback = true;
00418
00419 if (archiveName.right(1) != "/") {
00420 archiveName.append("/");
00421 }
00422
00423
00424 foreach ( const QModelIndex &index, indexes ) {
00425
00426
00427 if (index.column() != 0) continue;
00428
00429 QString file = archiveName + static_cast<ArchiveNode*>( index.internalPointer() )->entry()[ InternalID ].toString();
00430 files << file;
00431 }
00432
00433 KUrl::List kiolist(files);
00434
00435
00436 QMimeData *data = new QMimeData();
00437 data->setData("application/x-kde-dndextract",
00438 QDBusConnection::sessionBus().baseService().toUtf8()
00439 );
00440
00441
00442 if (!noFallback)
00443 kiolist.populateMimeData(data);
00444 return data;
00445 }
00446
00447 bool ArchiveModel::dropMimeData ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
00448 {
00449 Q_UNUSED( action );
00450 Q_UNUSED(parent );
00451 Q_UNUSED( column );
00452 Q_UNUSED( row );
00453
00454 if (!data->hasUrls())
00455 return false;
00456
00457 QStringList paths;
00458 foreach(const QUrl &url, data->urls()) {
00459 paths << url.path();
00460 }
00461
00462 emit droppedFiles(paths);
00463
00464 return true;
00465 }
00466
00467 ArchiveDirNode* ArchiveModel::parentFor( const ArchiveEntry& entry )
00468 {
00469 QStringList pieces = entry[ FileName ].toString().split( '/', QString::SkipEmptyParts );
00470 pieces.removeLast();
00471
00472 ArchiveDirNode *parent = m_rootNode;
00473
00474 foreach( const QString &piece, pieces )
00475 {
00476 ArchiveNode *node = parent->find( piece );
00477 if ( !node )
00478 {
00479 ArchiveEntry e;
00480 e[ FileName ] = parent->entry()[ FileName ].toString() + '/' + piece;
00481 e[ IsDirectory ] = true;
00482 node = new ArchiveDirNode( parent, e );
00483 insertNode( node );
00484 }
00485 if ( !node->isDir() )
00486 {
00487 ArchiveEntry e( node->entry() );
00488 node = new ArchiveDirNode( parent, e );
00489
00490
00491 insertNode( node );
00492 }
00493 parent = static_cast<ArchiveDirNode*>( node );
00494 }
00495
00496 return parent;
00497 }
00498 QModelIndex ArchiveModel::indexForNode( ArchiveNode *node )
00499 {
00500 Q_ASSERT( node );
00501 if ( node != m_rootNode )
00502 {
00503 Q_ASSERT( node->parent() );
00504 Q_ASSERT( node->parent()->isDir() );
00505 return createIndex( node->row(), 0, node );
00506 }
00507 return QModelIndex();
00508 }
00509
00510 void ArchiveModel::slotEntryRemoved( const QString & path )
00511 {
00512
00513 kDebug (1601) << "Removed node at path " << path;
00514 ArchiveNode *entry = m_rootNode->findByPath( path );
00515 if ( entry )
00516 {
00517 ArchiveDirNode *parent = entry->parent();
00518 QModelIndex index = indexForNode( entry );
00519
00520 beginRemoveRows( indexForNode( parent ), entry->row(), entry->row() );
00521
00522 delete parent->entries()[ entry->row() ];
00523 parent->entries()[ entry->row() ] = 0;
00524
00525 endRemoveRows();
00526 } else
00527 kDebug (1601) << "Did not find the removed node";
00528 }
00529
00530 void ArchiveModel::slotUserQuery(Query *query)
00531 {
00532 query->execute();
00533 }
00534
00535 void ArchiveModel::slotNewEntry( const ArchiveEntry& entry )
00536 {
00537 kDebug (1601) << entry;
00538
00539
00540
00541 if (m_showColumns.isEmpty()) {
00542
00543
00544 static const QList<int> columnsForDisplay =
00545 QList<int>()
00546 << FileName
00547 << Size
00548 << CompressedSize
00549 << Permissions
00550 << Owner
00551 << Group
00552 << Ratio
00553 << CRC
00554 << Method
00555 << Version
00556 << Timestamp
00557 << Comment;
00558
00559 QList<int> toInsert;
00560
00561 foreach(int column, columnsForDisplay) {
00562 if (entry.contains(column))
00563 toInsert << column;
00564 }
00565 beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1 );
00566 m_showColumns << toInsert;
00567 endInsertColumns();
00568
00569 kDebug( 1601 ) << "Show columns detected: " << m_showColumns;
00570
00571 }
00572
00574 if (m_rootNode){
00575 ArchiveNode *existing = m_rootNode->findByPath( entry[ FileName ].toString() );
00576 if ( existing ) {
00577 kDebug (1601) << "Refreshing entry for" << entry[FileName].toString();
00578
00579 existing->setEntry(entry);
00580 return;
00581 }
00582 }
00583
00585 ArchiveDirNode *parent = parentFor( entry );
00586
00588 QString name = entry[ FileName ].toString().split( '/', QString::SkipEmptyParts ).last();
00589 ArchiveNode *node = parent->find( name );
00590 if ( node )
00591 {
00592 node->setEntry( entry );
00593 }
00594 else
00595 {
00596 if ( entry[ FileName ].toString().endsWith( '/' ) || ( entry.contains( IsDirectory ) && entry[ IsDirectory ].toBool() ) )
00597 {
00598 node = new ArchiveDirNode( parent, entry );
00599 }
00600 else
00601 {
00602 node = new ArchiveNode( parent, entry );
00603 }
00604 insertNode( node );
00605 }
00606 }
00607
00608 void ArchiveModel::insertNode( ArchiveNode *node )
00609 {
00610 Q_ASSERT(node);
00611 ArchiveDirNode *parent = node->parent();
00612 Q_ASSERT(parent);
00613 beginInsertRows( indexForNode( parent ), parent->entries().count(), parent->entries().count() );
00614 parent->entries().append( node );
00615 endInsertRows();
00616 }
00617
00618 void ArchiveModel::setArchive( Kerfuffle::Archive *archive )
00619 {
00620 delete m_archive;
00621 m_archive = archive;
00622 m_rootNode->clear();
00623
00624
00625 m_showColumns.clear();
00626
00627 if ( m_archive )
00628 {
00629 Kerfuffle::ListJob *job = m_archive->list();
00630
00631 connect( job, SIGNAL( newEntry( const ArchiveEntry& ) ),
00632 this, SLOT( slotNewEntry( const ArchiveEntry& ) ) );
00633
00634 connect( job, SIGNAL( result( KJob * ) ),
00635 this, SIGNAL( loadingFinished(KJob *) ) );
00636
00637 if ( m_jobTracker )
00638 {
00639 m_jobTracker->registerJob( job );
00640 }
00641
00642 emit loadingStarted();
00643 job->start();
00644 }
00645 reset();
00646 }
00647
00648 ExtractJob* ArchiveModel::extractFile( const QVariant& fileName, const QString & destinationDir, Archive::CopyFlags flags ) const
00649 {
00650 QList<QVariant> files;
00651 files << fileName;
00652 return extractFiles( files, destinationDir, flags );
00653 }
00654
00655 ExtractJob* ArchiveModel::extractFiles( const QList<QVariant>& files, const QString & destinationDir, Kerfuffle::Archive::CopyFlags flags ) const
00656 {
00657 Q_ASSERT( m_archive );
00658 ExtractJob *newJob = m_archive->copyFiles( files, destinationDir, flags );
00659 connect(newJob, SIGNAL(userQuery(Query*)),
00660 this, SLOT(slotUserQuery(Query*)));
00661 return newJob;
00662 }
00663
00664 AddJob* ArchiveModel::addFiles( const QStringList & paths )
00665 {
00666 Q_ASSERT( m_archive );
00667
00668 if ( !m_archive->isReadOnly())
00669 {
00670 AddJob *job = m_archive->addFiles( paths );
00671 m_jobTracker->registerJob( job );
00672 connect( job, SIGNAL( newEntry( const ArchiveEntry& ) ),
00673 this, SLOT( slotNewEntry( const ArchiveEntry& ) ) );
00674 connect(job, SIGNAL(userQuery(Query*)),
00675 this, SLOT(slotUserQuery(Query*)));
00676
00677
00678 return job;
00679 }
00680 return 0;
00681 }
00682
00683 DeleteJob* ArchiveModel::deleteFiles( const QList<QVariant> & files )
00684 {
00685 Q_ASSERT( m_archive );
00686 if ( !m_archive->isReadOnly() )
00687 {
00688 DeleteJob *job = m_archive->deleteFiles( files );
00689 m_jobTracker->registerJob( job );
00690 connect( job, SIGNAL( entryRemoved( const QString & ) ),
00691 this, SLOT( slotEntryRemoved( const QString & ) ) );
00692
00693 connect(job, SIGNAL(userQuery(Query*)),
00694 this, SLOT(slotUserQuery(Query*)));
00695 return job;
00696 }
00697 return 0;
00698 }