29 #include <KIconLoader>
32 #include <KIO/NetAccess>
36 #include <QLatin1String>
39 #include <QPersistentModelIndex>
41 #include <QtDBus/QtDBus>
43 using namespace Kerfuffle;
49 K_GLOBAL_STATIC(QStringList, s_previousPieces)
58 ArchiveNode(ArchiveDirNode *parent,
const ArchiveEntry & entry)
64 virtual ~ArchiveNode()
77 const QStringList pieces = entry[
FileName].toString().split(QLatin1Char(
'/' ), QString::SkipEmptyParts);
78 m_name = pieces.isEmpty() ? QString() : pieces.last();
81 m_icon = KIconLoader::global()->loadMimeTypeIcon(KMimeType::mimeType(QLatin1String(
"inode/directory"))->iconName(), KIconLoader::Small);
83 const KMimeType::Ptr mimeType = KMimeType::findByPath(m_entry[
FileName].toString(), 0,
true);
84 m_icon = KIconLoader::global()->loadMimeTypeIcon(mimeType->iconName(), KIconLoader::Small);
88 ArchiveDirNode *parent()
const
95 virtual bool isDir()
const
111 void setIcon(
const QPixmap &icon)
120 ArchiveDirNode *m_parent;
124 class ArchiveDirNode:
public ArchiveNode
127 ArchiveDirNode(ArchiveDirNode *parent,
const ArchiveEntry & entry)
128 : ArchiveNode(parent, entry)
137 QList<ArchiveNode*> entries()
142 void setEntryAt(
int index, ArchiveNode* value)
144 m_entries[index] = value;
147 void appendEntry(ArchiveNode* entry)
149 m_entries.append(entry);
152 void removeEntryAt(
int index)
154 delete m_entries.takeAt(index);
157 virtual bool isDir()
const
162 ArchiveNode* find(
const QString & name)
164 foreach(ArchiveNode *node, m_entries) {
165 if (node && (node->name() == name)) {
172 ArchiveNode* findByPath(
const QStringList & pieces,
int index = 0)
174 if (index == pieces.count()) {
178 ArchiveNode *next = find(pieces.at(index));
180 if (index == pieces.count() - 1) {
183 if (next && next->isDir()) {
184 return static_cast<ArchiveDirNode*
>(next)->findByPath(pieces, index + 1);
189 void returnDirNodes(QList<ArchiveDirNode*> *store)
191 foreach(ArchiveNode *node, m_entries) {
193 store->prepend(static_cast<ArchiveDirNode*>(node));
194 static_cast<ArchiveDirNode*
>(node)->returnDirNodes(store);
201 qDeleteAll(m_entries);
206 QList<ArchiveNode*> m_entries;
216 class ArchiveModelSorter
219 ArchiveModelSorter(
int column, Qt::SortOrder order)
220 : m_sortColumn(column)
225 virtual ~ArchiveModelSorter()
229 inline bool operator()(
const QPair<ArchiveNode*, int> &left,
const QPair<ArchiveNode*, int> &right)
const
231 if (m_sortOrder == Qt::AscendingOrder) {
232 return lessThan(left, right);
234 return !lessThan(left, right);
239 bool lessThan(
const QPair<ArchiveNode*, int> &left,
const QPair<ArchiveNode*, int> &right)
const
241 const ArchiveNode *
const leftNode = left.first;
242 const ArchiveNode *
const rightNode = right.first;
245 if ((leftNode->isDir()) && (!rightNode->isDir())) {
246 return (m_sortOrder == Qt::AscendingOrder);
247 }
else if ((!leftNode->isDir()) && (rightNode->isDir())) {
248 return !(m_sortOrder == Qt::AscendingOrder);
251 const QVariant &leftEntry = leftNode->entry()[m_sortColumn];
252 const QVariant &rightEntry = rightNode->entry()[m_sortColumn];
254 switch (m_sortColumn) {
256 return leftNode->name() < rightNode->name();
259 return leftEntry.toInt() < rightEntry.toInt();
261 return leftEntry.toString() < rightEntry.toString();
271 Qt::SortOrder m_sortOrder;
274 int ArchiveNode::row()
const
277 return parent()->entries().indexOf(const_cast<ArchiveNode*>(
this));
285 , m_dbusPathName(dbusPathName)
297 if (index.isValid()) {
298 ArchiveNode *node =
static_cast<ArchiveNode*
>(index.internalPointer());
300 case Qt::DisplayRole: {
302 int columnId = m_showColumns.at(index.column());
310 const int children =
childCount(index, dirs, files);
311 return KIO::itemsSummaryString(children, files, dirs, 0,
false);
312 }
else if (node->entry().contains(
Link)) {
315 return KIO::convertSize(node->entry()[
Size ].toULongLong());
318 if (node->isDir() || node->entry().contains(
Link)) {
321 qulonglong compressedSize = node->entry()[
CompressedSize ].toULongLong();
322 if (compressedSize != 0) {
323 return KIO::convertSize(compressedSize);
329 if (node->isDir() || node->entry().contains(
Link)) {
332 qulonglong compressedSize = node->entry()[
CompressedSize ].toULongLong();
333 qulonglong size = node->entry()[
Size ].toULongLong();
334 if (compressedSize == 0 || size == 0) {
337 int ratio = int(100 * ((
double)size - compressedSize) / size);
338 return QString(QString::number(ratio) + QLatin1String(
" %" ));
343 const QDateTime timeStamp = node->entry().value(
Timestamp).toDateTime();
344 return KGlobal::locale()->formatDateTime(timeStamp);
348 return node->entry().value(columnId);
352 case Qt::DecorationRole:
353 if (index.column() == 0) {
371 Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
373 if (index.isValid()) {
374 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags;
382 if (role == Qt::DisplayRole) {
383 if (section >= m_showColumns.size()) {
384 kDebug() <<
"WEIRD: showColumns.size = " << m_showColumns.size()
385 <<
" and section = " << section;
389 int columnId = m_showColumns.at(section);
393 return i18nc(
"Name of a file inside an archive",
"Name");
395 return i18nc(
"Uncompressed size of a file inside an archive",
"Size");
397 return i18nc(
"Compressed size of a file inside an archive",
"Compressed");
399 return i18nc(
"Compression rate of file",
"Rate");
401 return i18nc(
"File's owner username",
"Owner");
403 return i18nc(
"File's group",
"Group");
405 return i18nc(
"File permissions",
"Mode");
407 return i18nc(
"CRC hash code",
"CRC");
409 return i18nc(
"Compression method",
"Method");
412 return i18nc(
"File version",
"Version");
414 return i18nc(
"Timestamp",
"Date");
416 return i18nc(
"File comment",
"Comment");
418 return i18nc(
"Unnamed column",
"??");
427 if (hasIndex(row, column, parent)) {
428 ArchiveDirNode *parentNode = parent.isValid() ?
static_cast<ArchiveDirNode*
>(parent.internalPointer()) : m_rootNode;
430 Q_ASSERT(parentNode->isDir());
432 ArchiveNode *item = parentNode->entries().value(row, 0);
434 return createIndex(row, column, item);
438 return QModelIndex();
443 if (index.isValid()) {
444 ArchiveNode *item =
static_cast<ArchiveNode*
>(index.internalPointer());
446 if (item->parent() && (item->parent() != m_rootNode)) {
447 return createIndex(item->parent()->row(), 0, item->parent());
450 return QModelIndex();
455 if (index.isValid()) {
456 ArchiveNode *item =
static_cast<ArchiveNode*
>(index.internalPointer());
458 return item->entry();
465 if (index.isValid()) {
467 ArchiveNode *item =
static_cast<ArchiveNode*
>(index.internalPointer());
470 const QList<ArchiveNode*> entries =
static_cast<ArchiveDirNode*
>(item)->entries();
471 foreach(
const ArchiveNode *node, entries) {
478 return entries.count();
487 if (parent.column() <= 0) {
488 ArchiveNode *parentNode = parent.isValid() ?
static_cast<ArchiveNode*
>(parent.internalPointer()) : m_rootNode;
490 if (parentNode && parentNode->isDir()) {
491 return static_cast<ArchiveDirNode*
>(parentNode)->entries().count();
499 return m_showColumns.size();
500 if (parent.isValid()) {
501 return static_cast<ArchiveNode*
>(parent.internalPointer())->entry().size();
507 if (m_showColumns.size() <= column) {
511 emit layoutAboutToBeChanged();
513 QList<ArchiveDirNode*> dirNodes;
514 m_rootNode->returnDirNodes(&dirNodes);
515 dirNodes.append(m_rootNode);
517 const ArchiveModelSorter modelSorter(m_showColumns.at(column), order);
519 foreach(ArchiveDirNode* dir, dirNodes) {
520 QVector < QPair<ArchiveNode*,int> > sorting(dir->entries().count());
521 for (
int i = 0; i < dir->entries().count(); ++i) {
522 ArchiveNode *item = dir->entries().at(i);
523 sorting[i].first = item;
524 sorting[i].second = i;
527 qStableSort(sorting.begin(), sorting.end(), modelSorter);
529 QModelIndexList fromIndexes;
530 QModelIndexList toIndexes;
531 for (
int r = 0; r < sorting.count(); ++r) {
532 ArchiveNode *item = sorting.at(r).first;
533 toIndexes.append(createIndex(r, 0, item));
534 fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first));
535 dir->setEntryAt(r, sorting.at(r).first);
538 changePersistentIndexList(fromIndexes, toIndexes);
541 index(0, 0, indexForNode(dir)),
542 index(dir->entries().size() - 1, 0, indexForNode(dir)));
545 emit layoutChanged();
550 return Qt::CopyAction | Qt::MoveAction;
558 types << QLatin1String(
"text/uri-list")
559 << QLatin1String(
"text/plain")
560 << QLatin1String(
"text/x-moz-url");
563 types << QLatin1String(
"application/x-kde-ark-dndextract-service")
564 << QLatin1String(
"application/x-kde-ark-dndextract-path");
573 QMimeData *
mimeData =
new QMimeData;
574 mimeData->setData(QLatin1String(
"application/x-kde-ark-dndextract-service"),
575 QDBusConnection::sessionBus().baseService().toUtf8());
576 mimeData->setData(QLatin1String(
"application/x-kde-ark-dndextract-path"),
577 m_dbusPathName.toUtf8());
589 if (!data->hasUrls()) {
594 foreach(
const QUrl &url, data->urls()) {
595 paths << url.toLocalFile();
602 if (parent.isValid()) {
603 QModelIndex droppedOnto =
index(row, column, parent);
605 kDebug() <<
"Using entry";
612 kDebug() <<
"Dropped onto " << path;
622 QString ArchiveModel::cleanFileName(
const QString& fileName)
624 if ((fileName == QLatin1String(
"/")) ||
625 (fileName == QLatin1String(
"."))) {
627 }
else if (fileName.startsWith(QLatin1String(
"./"))) {
628 return fileName.mid(2);
634 ArchiveDirNode* ArchiveModel::parentFor(
const ArchiveEntry& entry)
636 QStringList pieces = entry[
FileName ].toString().split(QLatin1Char(
'/' ), QString::SkipEmptyParts);
637 if (pieces.isEmpty()) {
645 if (s_previousPieces->count() == pieces.count()) {
649 for (
int i = 0; i < s_previousPieces->count(); ++i) {
650 if (s_previousPieces->at(i) != pieces.at(i)) {
663 ArchiveDirNode *
parent = m_rootNode;
665 foreach(
const QString &piece, pieces) {
666 ArchiveNode *node = parent->find(piece);
669 e[
FileName ] = (parent == m_rootNode) ?
670 piece : parent->entry()[
FileName ].toString() + QLatin1Char(
'/' ) + piece;
672 node =
new ArchiveDirNode(parent, e);
675 if (!node->isDir()) {
677 node =
new ArchiveDirNode(parent, e);
682 parent =
static_cast<ArchiveDirNode*
>(node);
686 *s_previousPieces = pieces;
690 QModelIndex ArchiveModel::indexForNode(ArchiveNode *node)
693 if (node != m_rootNode) {
694 Q_ASSERT(node->parent());
695 Q_ASSERT(node->parent()->isDir());
696 return createIndex(node->row(), 0, node);
698 return QModelIndex();
701 void ArchiveModel::slotEntryRemoved(
const QString & path)
703 kDebug() <<
"Removed node at path " << path;
705 const QString entryFileName(cleanFileName(path));
706 if (entryFileName.isEmpty()) {
710 ArchiveNode *entry = m_rootNode->findByPath(entryFileName.split(QLatin1Char(
'/' ), QString::SkipEmptyParts));
712 ArchiveDirNode *parent = entry->parent();
713 QModelIndex index = indexForNode(entry);
715 beginRemoveRows(indexForNode(parent), entry->row(), entry->row());
719 parent->removeEntryAt(entry->row());
723 kDebug() <<
"Did not find the removed node";
732 void ArchiveModel::slotNewEntryFromSetArchive(
const ArchiveEntry& entry)
738 m_newArchiveEntries.push_back(entry);
741 void ArchiveModel::slotNewEntry(
const ArchiveEntry& entry)
743 newEntry(entry, NotifyViews);
746 void ArchiveModel::newEntry(
const ArchiveEntry& receivedEntry, InsertBehaviour behaviour)
748 if (receivedEntry[
FileName].toString().isEmpty()) {
749 kDebug() <<
"Weird, received empty entry (no filename) - skipping";
755 if (m_showColumns.isEmpty()) {
757 static const QList<int> columnsForDisplay =
774 foreach(
int column, columnsForDisplay) {
775 if (receivedEntry.contains(column)) {
779 beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1);
780 m_showColumns << toInsert;
783 kDebug() <<
"Show columns detected: " << m_showColumns;
791 QString entryFileName = cleanFileName(entry[
FileName].toString());
792 if (entryFileName.isEmpty()) {
799 ArchiveNode *existing = m_rootNode->findByPath(entry[
FileName ].toString().split(QLatin1Char(
'/' )));
801 kDebug() <<
"Refreshing entry for" << entry[
FileName].toString();
805 qulonglong currentCompressedSize = existing->entry()[
CompressedSize].toULongLong();
809 existing->setEntry(entry);
815 ArchiveDirNode *parent = parentFor(entry);
818 QString name = entry[
FileName ].toString().split(QLatin1Char(
'/' ), QString::SkipEmptyParts).last();
819 ArchiveNode *node = parent->find(name);
821 node->setEntry(entry);
824 node =
new ArchiveDirNode(parent, entry);
826 node =
new ArchiveNode(parent, entry);
828 insertNode(node, behaviour);
832 void ArchiveModel::slotLoadingFinished(
KJob *job)
835 foreach(
const ArchiveEntry &entry, m_newArchiveEntries) {
836 newEntry(entry, DoNotNotifyViews);
839 m_newArchiveEntries.clear();
844 void ArchiveModel::insertNode(ArchiveNode *node, InsertBehaviour behaviour)
847 ArchiveDirNode *parent = node->parent();
849 if (behaviour == NotifyViews) {
850 beginInsertRows(indexForNode(parent), parent->entries().count(), parent->entries().count());
852 parent->appendEntry(node);
853 if (behaviour == NotifyViews) {
860 return m_archive.data();
865 m_archive.reset(archive);
869 s_previousPieces->clear();
873 m_newArchiveEntries.clear();
875 job = m_archive->list();
880 connect(job, SIGNAL(result(
KJob*)),
881 this, SLOT(slotLoadingFinished(
KJob*)));
889 m_showColumns.clear();
897 QList<QVariant> files;
905 ExtractJob *newJob = m_archive->copyFiles(files, destinationDir, options);
917 if (!m_archive->isReadOnly()) {
918 AddJob *job = m_archive->addFiles(filenames, options);
933 if (!m_archive->isReadOnly()) {
934 DeleteJob *job = m_archive->deleteFiles(files);
935 connect(job, SIGNAL(entryRemoved(QString)),
936 this, SLOT(slotEntryRemoved(QString)));
938 connect(job, SIGNAL(finished(
KJob*)),
939 this, SLOT(slotCleanupEmptyDirs()));
948 void ArchiveModel::slotCleanupEmptyDirs()
951 QList<QPersistentModelIndex> queue;
952 QList<QPersistentModelIndex> nodesToDelete;
955 for (
int i = 0; i <
rowCount(); ++i) {
956 queue.append(QPersistentModelIndex(
index(i, 0)));
960 while (!queue.isEmpty()) {
961 QPersistentModelIndex node = queue.takeFirst();
965 if (!hasChildren(node)) {
967 nodesToDelete << node;
970 for (
int i = 0; i <
rowCount(node); ++i) {
971 queue.append(QPersistentModelIndex(
index(i, 0, node)));
976 foreach(
const QPersistentModelIndex& node, nodesToDelete) {
977 ArchiveNode *rawNode =
static_cast<ArchiveNode*
>(node.internalPointer());
978 kDebug() <<
"Delete with parent entries " << rawNode->parent()->entries() <<
" and row " << rawNode->row();
979 beginRemoveRows(
parent(node), rawNode->row(), rawNode->row());
980 rawNode->parent()->removeEntryAt(rawNode->row());
986 #include "archivemodel.moc"
The compression ratio for the entry.
The user the entry belongs to.
Kerfuffle::ExtractJob * extractFile(const QVariant &fileName, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
virtual QStringList mimeTypes() const
The entry is password-protected.
The entry is a directory.
QModelIndex parent(const QModelIndex &index) const
The compressed size for the entry.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
Kerfuffle::AddJob * addFiles(const QStringList &paths, const Kerfuffle::CompressionOptions &options=Kerfuffle::CompressionOptions())
The entry is a symbolic link.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const
The archiver version needed to extract the entry.
Qt::ItemFlags flags(const QModelIndex &index) const
QHash< int, QVariant > ArchiveEntry
KJob * setArchive(Kerfuffle::Archive *archive)
ArchiveModel(const QString &dbusPathName, QObject *parent=0)
QHash< QString, QVariant > CompressionOptions
These are the extra options for doing the compression.
int childCount(const QModelIndex &index, int &dirs, int &files) const
The timestamp for the current entry.
Kerfuffle::Archive * archive() const
void droppedFiles(const QStringList &files, const QString &path=QString())
Kerfuffle::DeleteJob * deleteFiles(const QList< QVariant > &files)
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
QVariant data(const QModelIndex &index, int role) const
QHash< QString, QVariant > ExtractionOptions
int rowCount(const QModelIndex &parent=QModelIndex()) const
The compression method used on the entry.
The entry's original size.
static ArchiveNode * s_previousMatch
virtual Qt::DropActions supportedDropActions() const
virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder)
int columnCount(const QModelIndex &parent=QModelIndex()) const
Kerfuffle::ExtractJob * extractFiles(const QList< QVariant > &files, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
The user group the entry belongs to.
The entry's ID for Ark's internal manipulation.
virtual void execute()=0
Execute the response.
void loadingFinished(KJob *)
Kerfuffle::ArchiveEntry entryForIndex(const QModelIndex &index)