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

ark

  • sources
  • kde-4.14
  • kdeutils
  • ark
  • part
archivemodel.cpp
Go to the documentation of this file.
1 /*
2  * ark -- archiver for the KDE project
3  *
4  * Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
5  * Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
6  * Copyright (C) 2010-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "archivemodel.h"
25 #include "kerfuffle/archive.h"
26 #include "kerfuffle/jobs.h"
27 
28 #include <KDebug>
29 #include <KIconLoader>
30 #include <KLocale>
31 #include <KMimeType>
32 #include <KIO/NetAccess>
33 
34 #include <QDir>
35 #include <QFont>
36 #include <QLatin1String>
37 #include <QList>
38 #include <QMimeData>
39 #include <QPersistentModelIndex>
40 #include <QPixmap>
41 #include <QtDBus/QtDBus>
42 
43 using namespace Kerfuffle;
44 
45 class ArchiveDirNode;
46 
47 //used to speed up the loading of large archives
48 static ArchiveNode* s_previousMatch = NULL;
49 K_GLOBAL_STATIC(QStringList, s_previousPieces)
50 
51 
52 // TODO: This class hierarchy needs some love.
53 // Having a parent take a child class as a parameter in the constructor
54 // should trigger one's spider-sense (TM).
55 class ArchiveNode
56 {
57 public:
58  ArchiveNode(ArchiveDirNode *parent, const ArchiveEntry & entry)
59  : m_parent(parent)
60  {
61  setEntry(entry);
62  }
63 
64  virtual ~ArchiveNode()
65  {
66  }
67 
68  const ArchiveEntry &entry() const
69  {
70  return m_entry;
71  }
72 
73  void setEntry(const ArchiveEntry& entry)
74  {
75  m_entry = entry;
76 
77  const QStringList pieces = entry[FileName].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
78  m_name = pieces.isEmpty() ? QString() : pieces.last();
79 
80  if (entry[IsDirectory].toBool()) {
81  m_icon = KIconLoader::global()->loadMimeTypeIcon(KMimeType::mimeType(QLatin1String("inode/directory"))->iconName(), KIconLoader::Small);
82  } else {
83  const KMimeType::Ptr mimeType = KMimeType::findByPath(m_entry[FileName].toString(), 0, true);
84  m_icon = KIconLoader::global()->loadMimeTypeIcon(mimeType->iconName(), KIconLoader::Small);
85  }
86  }
87 
88  ArchiveDirNode *parent() const
89  {
90  return m_parent;
91  }
92 
93  int row() const;
94 
95  virtual bool isDir() const
96  {
97  return false;
98  }
99 
100  QPixmap icon() const
101  {
102  return m_icon;
103  }
104 
105  QString name() const
106  {
107  return m_name;
108  }
109 
110 protected:
111  void setIcon(const QPixmap &icon)
112  {
113  m_icon = icon;
114  }
115 
116 private:
117  ArchiveEntry m_entry;
118  QPixmap m_icon;
119  QString m_name;
120  ArchiveDirNode *m_parent;
121 };
122 
123 
124 class ArchiveDirNode: public ArchiveNode
125 {
126 public:
127  ArchiveDirNode(ArchiveDirNode *parent, const ArchiveEntry & entry)
128  : ArchiveNode(parent, entry)
129  {
130  }
131 
132  ~ArchiveDirNode()
133  {
134  clear();
135  }
136 
137  QList<ArchiveNode*> entries()
138  {
139  return m_entries;
140  }
141 
142  void setEntryAt(int index, ArchiveNode* value)
143  {
144  m_entries[index] = value;
145  }
146 
147  void appendEntry(ArchiveNode* entry)
148  {
149  m_entries.append(entry);
150  }
151 
152  void removeEntryAt(int index)
153  {
154  delete m_entries.takeAt(index);
155  }
156 
157  virtual bool isDir() const
158  {
159  return true;
160  }
161 
162  ArchiveNode* find(const QString & name)
163  {
164  foreach(ArchiveNode *node, m_entries) {
165  if (node && (node->name() == name)) {
166  return node;
167  }
168  }
169  return 0;
170  }
171 
172  ArchiveNode* findByPath(const QStringList & pieces, int index = 0)
173  {
174  if (index == pieces.count()) {
175  return 0;
176  }
177 
178  ArchiveNode *next = find(pieces.at(index));
179 
180  if (index == pieces.count() - 1) {
181  return next;
182  }
183  if (next && next->isDir()) {
184  return static_cast<ArchiveDirNode*>(next)->findByPath(pieces, index + 1);
185  }
186  return 0;
187  }
188 
189  void returnDirNodes(QList<ArchiveDirNode*> *store)
190  {
191  foreach(ArchiveNode *node, m_entries) {
192  if (node->isDir()) {
193  store->prepend(static_cast<ArchiveDirNode*>(node));
194  static_cast<ArchiveDirNode*>(node)->returnDirNodes(store);
195  }
196  }
197  }
198 
199  void clear()
200  {
201  qDeleteAll(m_entries);
202  m_entries.clear();
203  }
204 
205 private:
206  QList<ArchiveNode*> m_entries;
207 };
208 
216 class ArchiveModelSorter
217 {
218 public:
219  ArchiveModelSorter(int column, Qt::SortOrder order)
220  : m_sortColumn(column)
221  , m_sortOrder(order)
222  {
223  }
224 
225  virtual ~ArchiveModelSorter()
226  {
227  }
228 
229  inline bool operator()(const QPair<ArchiveNode*, int> &left, const QPair<ArchiveNode*, int> &right) const
230  {
231  if (m_sortOrder == Qt::AscendingOrder) {
232  return lessThan(left, right);
233  } else {
234  return !lessThan(left, right);
235  }
236  }
237 
238 protected:
239  bool lessThan(const QPair<ArchiveNode*, int> &left, const QPair<ArchiveNode*, int> &right) const
240  {
241  const ArchiveNode * const leftNode = left.first;
242  const ArchiveNode * const rightNode = right.first;
243 
244  // #234373: sort folders before files
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);
249  }
250 
251  const QVariant &leftEntry = leftNode->entry()[m_sortColumn];
252  const QVariant &rightEntry = rightNode->entry()[m_sortColumn];
253 
254  switch (m_sortColumn) {
255  case FileName:
256  return leftNode->name() < rightNode->name();
257  case Size:
258  case CompressedSize:
259  return leftEntry.toInt() < rightEntry.toInt();
260  default:
261  return leftEntry.toString() < rightEntry.toString();
262  }
263 
264  // We should not get here.
265  Q_ASSERT(false);
266  return false;
267  }
268 
269 private:
270  int m_sortColumn;
271  Qt::SortOrder m_sortOrder;
272 };
273 
274 int ArchiveNode::row() const
275 {
276  if (parent()) {
277  return parent()->entries().indexOf(const_cast<ArchiveNode*>(this));
278  }
279  return 0;
280 }
281 
282 ArchiveModel::ArchiveModel(const QString &dbusPathName, QObject *parent)
283  : QAbstractItemModel(parent)
284  , m_rootNode(new ArchiveDirNode(0, ArchiveEntry()))
285  , m_dbusPathName(dbusPathName)
286 {
287 }
288 
289 ArchiveModel::~ArchiveModel()
290 {
291  delete m_rootNode;
292  m_rootNode = 0;
293 }
294 
295 QVariant ArchiveModel::data(const QModelIndex &index, int role) const
296 {
297  if (index.isValid()) {
298  ArchiveNode *node = static_cast<ArchiveNode*>(index.internalPointer());
299  switch (role) {
300  case Qt::DisplayRole: {
301  //TODO: complete the columns
302  int columnId = m_showColumns.at(index.column());
303  switch (columnId) {
304  case FileName:
305  return node->name();
306  case Size:
307  if (node->isDir()) {
308  int dirs;
309  int files;
310  const int children = childCount(index, dirs, files);
311  return KIO::itemsSummaryString(children, files, dirs, 0, false);
312  } else if (node->entry().contains(Link)) {
313  return QVariant();
314  } else {
315  return KIO::convertSize(node->entry()[ Size ].toULongLong());
316  }
317  case CompressedSize:
318  if (node->isDir() || node->entry().contains(Link)) {
319  return QVariant();
320  } else {
321  qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong();
322  if (compressedSize != 0) {
323  return KIO::convertSize(compressedSize);
324  } else {
325  return QVariant();
326  }
327  }
328  case Ratio: // TODO: Use node->entry()[Ratio] when available
329  if (node->isDir() || node->entry().contains(Link)) {
330  return QVariant();
331  } else {
332  qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong();
333  qulonglong size = node->entry()[ Size ].toULongLong();
334  if (compressedSize == 0 || size == 0) {
335  return QVariant();
336  } else {
337  int ratio = int(100 * ((double)size - compressedSize) / size);
338  return QString(QString::number(ratio) + QLatin1String( " %" ));
339  }
340  }
341 
342  case Timestamp: {
343  const QDateTime timeStamp = node->entry().value(Timestamp).toDateTime();
344  return KGlobal::locale()->formatDateTime(timeStamp);
345  }
346 
347  default:
348  return node->entry().value(columnId);
349  }
350  break;
351  }
352  case Qt::DecorationRole:
353  if (index.column() == 0) {
354  return node->icon();
355  }
356  return QVariant();
357  case Qt::FontRole: {
358  QFont f;
359  f.setItalic(node->entry()[ IsPasswordProtected ].toBool());
360  return f;
361  }
362  default:
363  return QVariant();
364  }
365  }
366  return QVariant();
367 }
368 
369 Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const
370 {
371  Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
372 
373  if (index.isValid()) {
374  return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags;
375  }
376 
377  return 0;
378 }
379 
380 QVariant ArchiveModel::headerData(int section, Qt::Orientation, int role) const
381 {
382  if (role == Qt::DisplayRole) {
383  if (section >= m_showColumns.size()) {
384  kDebug() << "WEIRD: showColumns.size = " << m_showColumns.size()
385  << " and section = " << section;
386  return QVariant();
387  }
388 
389  int columnId = m_showColumns.at(section);
390 
391  switch (columnId) {
392  case FileName:
393  return i18nc("Name of a file inside an archive", "Name");
394  case Size:
395  return i18nc("Uncompressed size of a file inside an archive", "Size");
396  case CompressedSize:
397  return i18nc("Compressed size of a file inside an archive", "Compressed");
398  case Ratio:
399  return i18nc("Compression rate of file", "Rate");
400  case Owner:
401  return i18nc("File's owner username", "Owner");
402  case Group:
403  return i18nc("File's group", "Group");
404  case Permissions:
405  return i18nc("File permissions", "Mode");
406  case CRC:
407  return i18nc("CRC hash code", "CRC");
408  case Method:
409  return i18nc("Compression method", "Method");
410  case Version:
411  //TODO: what exactly is a file version?
412  return i18nc("File version", "Version");
413  case Timestamp:
414  return i18nc("Timestamp", "Date");
415  case Comment:
416  return i18nc("File comment", "Comment");
417  default:
418  return i18nc("Unnamed column", "??");
419 
420  }
421  }
422  return QVariant();
423 }
424 
425 QModelIndex ArchiveModel::index(int row, int column, const QModelIndex &parent) const
426 {
427  if (hasIndex(row, column, parent)) {
428  ArchiveDirNode *parentNode = parent.isValid() ? static_cast<ArchiveDirNode*>(parent.internalPointer()) : m_rootNode;
429 
430  Q_ASSERT(parentNode->isDir());
431 
432  ArchiveNode *item = parentNode->entries().value(row, 0);
433  if (item) {
434  return createIndex(row, column, item);
435  }
436  }
437 
438  return QModelIndex();
439 }
440 
441 QModelIndex ArchiveModel::parent(const QModelIndex &index) const
442 {
443  if (index.isValid()) {
444  ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
445  Q_ASSERT(item);
446  if (item->parent() && (item->parent() != m_rootNode)) {
447  return createIndex(item->parent()->row(), 0, item->parent());
448  }
449  }
450  return QModelIndex();
451 }
452 
453 ArchiveEntry ArchiveModel::entryForIndex(const QModelIndex &index)
454 {
455  if (index.isValid()) {
456  ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
457  Q_ASSERT(item);
458  return item->entry();
459  }
460  return ArchiveEntry();
461 }
462 
463 int ArchiveModel::childCount(const QModelIndex &index, int &dirs, int &files) const
464 {
465  if (index.isValid()) {
466  dirs = files = 0;
467  ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
468  Q_ASSERT(item);
469  if (item->isDir()) {
470  const QList<ArchiveNode*> entries = static_cast<ArchiveDirNode*>(item)->entries();
471  foreach(const ArchiveNode *node, entries) {
472  if (node->isDir()) {
473  dirs++;
474  } else {
475  files++;
476  }
477  }
478  return entries.count();
479  }
480  return 0;
481  }
482  return -1;
483 }
484 
485 int ArchiveModel::rowCount(const QModelIndex &parent) const
486 {
487  if (parent.column() <= 0) {
488  ArchiveNode *parentNode = parent.isValid() ? static_cast<ArchiveNode*>(parent.internalPointer()) : m_rootNode;
489 
490  if (parentNode && parentNode->isDir()) {
491  return static_cast<ArchiveDirNode*>(parentNode)->entries().count();
492  }
493  }
494  return 0;
495 }
496 
497 int ArchiveModel::columnCount(const QModelIndex &parent) const
498 {
499  return m_showColumns.size();
500  if (parent.isValid()) {
501  return static_cast<ArchiveNode*>(parent.internalPointer())->entry().size();
502  }
503 }
504 
505 void ArchiveModel::sort(int column, Qt::SortOrder order)
506 {
507  if (m_showColumns.size() <= column) {
508  return;
509  }
510 
511  emit layoutAboutToBeChanged();
512 
513  QList<ArchiveDirNode*> dirNodes;
514  m_rootNode->returnDirNodes(&dirNodes);
515  dirNodes.append(m_rootNode);
516 
517  const ArchiveModelSorter modelSorter(m_showColumns.at(column), order);
518 
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;
525  }
526 
527  qStableSort(sorting.begin(), sorting.end(), modelSorter);
528 
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);
536  }
537 
538  changePersistentIndexList(fromIndexes, toIndexes);
539 
540  emit dataChanged(
541  index(0, 0, indexForNode(dir)),
542  index(dir->entries().size() - 1, 0, indexForNode(dir)));
543  }
544 
545  emit layoutChanged();
546 }
547 
548 Qt::DropActions ArchiveModel::supportedDropActions() const
549 {
550  return Qt::CopyAction | Qt::MoveAction;
551 }
552 
553 QStringList ArchiveModel::mimeTypes() const
554 {
555  QStringList types;
556 
557  // MIME types we accept for dragging (eg. Dolphin -> Ark).
558  types << QLatin1String("text/uri-list")
559  << QLatin1String("text/plain")
560  << QLatin1String("text/x-moz-url");
561 
562  // MIME types we accept for dropping (eg. Ark -> Dolphin).
563  types << QLatin1String("application/x-kde-ark-dndextract-service")
564  << QLatin1String("application/x-kde-ark-dndextract-path");
565 
566  return types;
567 }
568 
569 QMimeData *ArchiveModel::mimeData(const QModelIndexList &indexes) const
570 {
571  Q_UNUSED(indexes)
572 
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());
578 
579  return mimeData;
580 }
581 
582 bool ArchiveModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
583 {
584  Q_UNUSED(action)
585  Q_UNUSED(row)
586  Q_UNUSED(column)
587  Q_UNUSED(parent)
588 
589  if (!data->hasUrls()) {
590  return false;
591  }
592 
593  QStringList paths;
594  foreach(const QUrl &url, data->urls()) {
595  paths << url.toLocalFile();
596  }
597 
598  //for now, this code is not used because adding files to paths inside the
599  //archive is not supported yet. need a solution for this later.
600  QString path;
601 #if 0
602  if (parent.isValid()) {
603  QModelIndex droppedOnto = index(row, column, parent);
604  if (entryForIndex(droppedOnto).value(IsDirectory).toBool()) {
605  kDebug() << "Using entry";
606  path = entryForIndex(droppedOnto).value(FileName).toString();
607  } else {
608  path = entryForIndex(parent).value(FileName).toString();
609  }
610  }
611 
612  kDebug() << "Dropped onto " << path;
613 
614 #endif
615 
616  emit droppedFiles(paths, path);
617 
618  return true;
619 }
620 
621 // For a rationale, see bugs #194241 and #241967
622 QString ArchiveModel::cleanFileName(const QString& fileName)
623 {
624  if ((fileName == QLatin1String("/")) ||
625  (fileName == QLatin1String("."))) { // "." is present in ISO files
626  return QString();
627  } else if (fileName.startsWith(QLatin1String("./"))) {
628  return fileName.mid(2);
629  }
630 
631  return fileName;
632 }
633 
634 ArchiveDirNode* ArchiveModel::parentFor(const ArchiveEntry& entry)
635 {
636  QStringList pieces = entry[ FileName ].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
637  if (pieces.isEmpty()) {
638  return NULL;
639  }
640  pieces.removeLast();
641 
642  if (s_previousMatch) {
643  //the number of path elements must be the same for the shortcut
644  //to work
645  if (s_previousPieces->count() == pieces.count()) {
646  bool equal = true;
647 
648  //make sure all the pieces match up
649  for (int i = 0; i < s_previousPieces->count(); ++i) {
650  if (s_previousPieces->at(i) != pieces.at(i)) {
651  equal = false;
652  break;
653  }
654  }
655 
656  //if match return it
657  if (equal) {
658  return static_cast<ArchiveDirNode*>(s_previousMatch);
659  }
660  }
661  }
662 
663  ArchiveDirNode *parent = m_rootNode;
664 
665  foreach(const QString &piece, pieces) {
666  ArchiveNode *node = parent->find(piece);
667  if (!node) {
668  ArchiveEntry e;
669  e[ FileName ] = (parent == m_rootNode) ?
670  piece : parent->entry()[ FileName ].toString() + QLatin1Char( '/' ) + piece;
671  e[ IsDirectory ] = true;
672  node = new ArchiveDirNode(parent, e);
673  insertNode(node);
674  }
675  if (!node->isDir()) {
676  ArchiveEntry e(node->entry());
677  node = new ArchiveDirNode(parent, e);
678  //Maybe we have both a file and a directory of the same name
679  // We avoid removing previous entries unless necessary
680  insertNode(node);
681  }
682  parent = static_cast<ArchiveDirNode*>(node);
683  }
684 
685  s_previousMatch = parent;
686  *s_previousPieces = pieces;
687 
688  return parent;
689 }
690 QModelIndex ArchiveModel::indexForNode(ArchiveNode *node)
691 {
692  Q_ASSERT(node);
693  if (node != m_rootNode) {
694  Q_ASSERT(node->parent());
695  Q_ASSERT(node->parent()->isDir());
696  return createIndex(node->row(), 0, node);
697  }
698  return QModelIndex();
699 }
700 
701 void ArchiveModel::slotEntryRemoved(const QString & path)
702 {
703  kDebug() << "Removed node at path " << path;
704 
705  const QString entryFileName(cleanFileName(path));
706  if (entryFileName.isEmpty()) {
707  return;
708  }
709 
710  ArchiveNode *entry = m_rootNode->findByPath(entryFileName.split(QLatin1Char( '/' ), QString::SkipEmptyParts));
711  if (entry) {
712  ArchiveDirNode *parent = entry->parent();
713  QModelIndex index = indexForNode(entry);
714 
715  beginRemoveRows(indexForNode(parent), entry->row(), entry->row());
716 
717  //delete parent->entries()[ entry->row() ];
718  //parent->entries()[ entry->row() ] = 0;
719  parent->removeEntryAt(entry->row());
720 
721  endRemoveRows();
722  } else {
723  kDebug() << "Did not find the removed node";
724  }
725 }
726 
727 void ArchiveModel::slotUserQuery(Kerfuffle::Query *query)
728 {
729  query->execute();
730 }
731 
732 void ArchiveModel::slotNewEntryFromSetArchive(const ArchiveEntry& entry)
733 {
734  // we cache all entries that appear when opening a new archive
735  // so we can all them together once it's done, this is a huge
736  // performance improvement because we save from doing lots of
737  // begin/endInsertRows
738  m_newArchiveEntries.push_back(entry);
739 }
740 
741 void ArchiveModel::slotNewEntry(const ArchiveEntry& entry)
742 {
743  newEntry(entry, NotifyViews);
744 }
745 
746 void ArchiveModel::newEntry(const ArchiveEntry& receivedEntry, InsertBehaviour behaviour)
747 {
748  if (receivedEntry[FileName].toString().isEmpty()) {
749  kDebug() << "Weird, received empty entry (no filename) - skipping";
750  return;
751  }
752 
753  //if there are no addidional columns registered, then have a look at the
754  //entry and populate some
755  if (m_showColumns.isEmpty()) {
756  //these are the columns we are interested in showing in the display
757  static const QList<int> columnsForDisplay =
758  QList<int>()
759  << FileName
760  << Size
761  << CompressedSize
762  << Permissions
763  << Owner
764  << Group
765  << Ratio
766  << CRC
767  << Method
768  << Version
769  << Timestamp
770  << Comment;
771 
772  QList<int> toInsert;
773 
774  foreach(int column, columnsForDisplay) {
775  if (receivedEntry.contains(column)) {
776  toInsert << column;
777  }
778  }
779  beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1);
780  m_showColumns << toInsert;
781  endInsertColumns();
782 
783  kDebug() << "Show columns detected: " << m_showColumns;
784  }
785 
786  //make a copy
787  ArchiveEntry entry = receivedEntry;
788 
789  //#194241: Filenames such as "./file" should be displayed as "file"
790  //#241967: Entries called "/" should be ignored
791  QString entryFileName = cleanFileName(entry[FileName].toString());
792  if (entryFileName.isEmpty()) { // The entry contains only "." or "./"
793  return;
794  }
795  entry[FileName] = entryFileName;
796 
798  if (m_rootNode) {
799  ArchiveNode *existing = m_rootNode->findByPath(entry[ FileName ].toString().split(QLatin1Char( '/' )));
800  if (existing) {
801  kDebug() << "Refreshing entry for" << entry[FileName].toString();
802 
803  // Multi-volume files are repeated at least in RAR archives.
804  // In that case, we need to sum the compressed size for each volume
805  qulonglong currentCompressedSize = existing->entry()[CompressedSize].toULongLong();
806  entry[CompressedSize] = currentCompressedSize + entry[CompressedSize].toULongLong();
807 
808  //TODO: benchmark whether it's a bad idea to reset the entry here.
809  existing->setEntry(entry);
810  return;
811  }
812  }
813 
815  ArchiveDirNode *parent = parentFor(entry);
816 
818  QString name = entry[ FileName ].toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts).last();
819  ArchiveNode *node = parent->find(name);
820  if (node) {
821  node->setEntry(entry);
822  } else {
823  if (entry[ FileName ].toString().endsWith(QLatin1Char( '/' )) || (entry.contains(IsDirectory) && entry[ IsDirectory ].toBool())) {
824  node = new ArchiveDirNode(parent, entry);
825  } else {
826  node = new ArchiveNode(parent, entry);
827  }
828  insertNode(node, behaviour);
829  }
830 }
831 
832 void ArchiveModel::slotLoadingFinished(KJob *job)
833 {
834  //kDebug() << entry;
835  foreach(const ArchiveEntry &entry, m_newArchiveEntries) {
836  newEntry(entry, DoNotNotifyViews);
837  }
838  reset();
839  m_newArchiveEntries.clear();
840 
841  emit loadingFinished(job);
842 }
843 
844 void ArchiveModel::insertNode(ArchiveNode *node, InsertBehaviour behaviour)
845 {
846  Q_ASSERT(node);
847  ArchiveDirNode *parent = node->parent();
848  Q_ASSERT(parent);
849  if (behaviour == NotifyViews) {
850  beginInsertRows(indexForNode(parent), parent->entries().count(), parent->entries().count());
851  }
852  parent->appendEntry(node);
853  if (behaviour == NotifyViews) {
854  endInsertRows();
855  }
856 }
857 
858 Kerfuffle::Archive* ArchiveModel::archive() const
859 {
860  return m_archive.data();
861 }
862 
863 KJob* ArchiveModel::setArchive(Kerfuffle::Archive *archive)
864 {
865  m_archive.reset(archive);
866 
867  m_rootNode->clear();
868  s_previousMatch = 0;
869  s_previousPieces->clear();
870 
871  Kerfuffle::ListJob *job = NULL;
872 
873  m_newArchiveEntries.clear();
874  if (m_archive) {
875  job = m_archive->list(); // TODO: call "open" or "create"?
876 
877  connect(job, SIGNAL(newEntry(ArchiveEntry)),
878  this, SLOT(slotNewEntryFromSetArchive(ArchiveEntry)));
879 
880  connect(job, SIGNAL(result(KJob*)),
881  this, SLOT(slotLoadingFinished(KJob*)));
882 
883  connect(job, SIGNAL(userQuery(Kerfuffle::Query*)),
884  this, SLOT(slotUserQuery(Kerfuffle::Query*)));
885 
886  emit loadingStarted();
887 
888  // TODO: make sure if it's ok to not have calls to beginRemoveColumns here
889  m_showColumns.clear();
890  }
891  reset();
892  return job;
893 }
894 
895 ExtractJob* ArchiveModel::extractFile(const QVariant& fileName, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const
896 {
897  QList<QVariant> files;
898  files << fileName;
899  return extractFiles(files, destinationDir, options);
900 }
901 
902 ExtractJob* ArchiveModel::extractFiles(const QList<QVariant>& files, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const
903 {
904  Q_ASSERT(m_archive);
905  ExtractJob *newJob = m_archive->copyFiles(files, destinationDir, options);
906  connect(newJob, SIGNAL(userQuery(Kerfuffle::Query*)),
907  this, SLOT(slotUserQuery(Kerfuffle::Query*)));
908  return newJob;
909 }
910 
911 AddJob* ArchiveModel::addFiles(const QStringList & filenames, const CompressionOptions& options)
912 {
913  if (!m_archive) {
914  return NULL;
915  }
916 
917  if (!m_archive->isReadOnly()) {
918  AddJob *job = m_archive->addFiles(filenames, options);
919  connect(job, SIGNAL(newEntry(ArchiveEntry)),
920  this, SLOT(slotNewEntry(ArchiveEntry)));
921  connect(job, SIGNAL(userQuery(Kerfuffle::Query*)),
922  this, SLOT(slotUserQuery(Kerfuffle::Query*)));
923 
924 
925  return job;
926  }
927  return 0;
928 }
929 
930 DeleteJob* ArchiveModel::deleteFiles(const QList<QVariant> & files)
931 {
932  Q_ASSERT(m_archive);
933  if (!m_archive->isReadOnly()) {
934  DeleteJob *job = m_archive->deleteFiles(files);
935  connect(job, SIGNAL(entryRemoved(QString)),
936  this, SLOT(slotEntryRemoved(QString)));
937 
938  connect(job, SIGNAL(finished(KJob*)),
939  this, SLOT(slotCleanupEmptyDirs()));
940 
941  connect(job, SIGNAL(userQuery(Kerfuffle::Query*)),
942  this, SLOT(slotUserQuery(Kerfuffle::Query*)));
943  return job;
944  }
945  return 0;
946 }
947 
948 void ArchiveModel::slotCleanupEmptyDirs()
949 {
950  kDebug();
951  QList<QPersistentModelIndex> queue;
952  QList<QPersistentModelIndex> nodesToDelete;
953 
954  //add root nodes
955  for (int i = 0; i < rowCount(); ++i) {
956  queue.append(QPersistentModelIndex(index(i, 0)));
957  }
958 
959  //breadth-first traverse
960  while (!queue.isEmpty()) {
961  QPersistentModelIndex node = queue.takeFirst();
962  ArchiveEntry entry = entryForIndex(node);
963  //kDebug() << "Trying " << entry[FileName].toString();
964 
965  if (!hasChildren(node)) {
966  if (!entry.contains(InternalID)) {
967  nodesToDelete << node;
968  }
969  } else {
970  for (int i = 0; i < rowCount(node); ++i) {
971  queue.append(QPersistentModelIndex(index(i, 0, node)));
972  }
973  }
974  }
975 
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());
981  endRemoveRows();
982  //kDebug() << "Removed entry " << entry[FileName].toString();
983  }
984 }
985 
986 #include "archivemodel.moc"
QAbstractItemModel::hasIndex
bool hasIndex(int row, int column, const QModelIndex &parent) const
Kerfuffle::Ratio
The compression ratio for the entry.
Definition: archive.h:67
Kerfuffle::Owner
The user the entry belongs to.
Definition: archive.h:62
QList::clear
void clear()
ArchiveModel::extractFile
Kerfuffle::ExtractJob * extractFile(const QVariant &fileName, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
Definition: archivemodel.cpp:895
QModelIndex
ArchiveModel::mimeTypes
virtual QStringList mimeTypes() const
Definition: archivemodel.cpp:553
Kerfuffle::IsPasswordProtected
The entry is password-protected.
Definition: archive.h:74
archivemodel.h
QAbstractItemModel::layoutChanged
void layoutChanged()
Kerfuffle::IsDirectory
The entry is a directory.
Definition: archive.h:72
Kerfuffle::Query
Definition: queries.h:44
QList::push_back
void push_back(const T &value)
Kerfuffle::DeleteJob
Definition: jobs.h:155
QString::split
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QFont
QList::at
const T & at(int i) const
QObject::children
const QObjectList & children() const
QAbstractItemModel::changePersistentIndexList
void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to)
Kerfuffle::AddJob
Definition: jobs.h:140
Kerfuffle::CompressedSize
The compressed size for the entry.
Definition: archive.h:65
QDBusConnection::sessionBus
QDBusConnection sessionBus()
archive.h
ArchiveModel::index
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Definition: archivemodel.cpp:425
ArchiveModel::mimeData
virtual QMimeData * mimeData(const QModelIndexList &indexes) const
Definition: archivemodel.cpp:569
ArchiveModel::addFiles
Kerfuffle::AddJob * addFiles(const QStringList &paths, const Kerfuffle::CompressionOptions &options=Kerfuffle::CompressionOptions())
Definition: archivemodel.cpp:911
QMimeData
Kerfuffle::Link
The entry is a symbolic link.
Definition: archive.h:66
ArchiveModel::headerData
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const
Definition: archivemodel.cpp:380
QList::size
int size() const
Kerfuffle::Version
The archiver version needed to extract the entry.
Definition: archive.h:70
Kerfuffle::ListJob
Definition: jobs.h:92
QScopedPointer::reset
void reset(T *other)
QAbstractItemModel::layoutAboutToBeChanged
void layoutAboutToBeChanged()
ArchiveModel::flags
Qt::ItemFlags flags(const QModelIndex &index) const
Definition: archivemodel.cpp:369
QAbstractItemModel::reset
void reset()
QModelIndex::isValid
bool isValid() const
ArchiveModel::loadingStarted
void loadingStarted()
QString::number
QString number(int n, int base)
QList::count
int count(const T &value) const
Kerfuffle::ArchiveEntry
QHash< int, QVariant > ArchiveEntry
Definition: archive.h:78
QList::append
void append(const T &value)
QVariant::toInt
int toInt(bool *ok) const
QAbstractItemModel::dataChanged
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
QHash
QAbstractItemModel::endInsertRows
void endInsertRows()
QObject
QList::isEmpty
bool isEmpty() const
QString::isEmpty
bool isEmpty() const
QAbstractItemModel::beginRemoveRows
void beginRemoveRows(const QModelIndex &parent, int first, int last)
QModelIndex::row
int row() const
ArchiveModel::setArchive
KJob * setArchive(Kerfuffle::Archive *archive)
Definition: archivemodel.cpp:863
QString::startsWith
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QModelIndex::internalPointer
void * internalPointer() const
ArchiveModel::ArchiveModel
ArchiveModel(const QString &dbusPathName, QObject *parent=0)
Definition: archivemodel.cpp:282
QString
QList< ArchiveNode * >
ArchiveModel::childCount
int childCount(const QModelIndex &index, int &dirs, int &files) const
Definition: archivemodel.cpp:463
Kerfuffle::Timestamp
The timestamp for the current entry.
Definition: archive.h:71
ArchiveModel::archive
Kerfuffle::Archive * archive() const
Definition: archivemodel.cpp:858
QStringList
QPair
Kerfuffle::FileName
The entry's file name.
Definition: archive.h:59
ArchiveModel::droppedFiles
void droppedFiles(const QStringList &files, const QString &path=QString())
QPixmap
Kerfuffle::Archive
Definition: archive.h:88
QUrl::toLocalFile
QString toLocalFile() const
QAbstractItemModel::createIndex
QModelIndex createIndex(int row, int column, void *ptr) const
ArchiveModel::deleteFiles
Kerfuffle::DeleteJob * deleteFiles(const QList< QVariant > &files)
Definition: archivemodel.cpp:930
QScopedPointer::data
T * data() const
QHash::value
const T value(const Key &key) const
ArchiveModel::dropMimeData
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
Definition: archivemodel.cpp:582
Kerfuffle::CRC
The entry's CRC.
Definition: archive.h:68
QUrl
QLatin1Char
Kerfuffle::ExtractJob
Definition: jobs.h:118
QFont::setItalic
void setItalic(bool enable)
QAbstractItemModel::beginInsertRows
void beginInsertRows(const QModelIndex &parent, int first, int last)
Kerfuffle::Permissions
The entry's permissions.
Definition: archive.h:61
jobs.h
QPersistentModelIndex
ArchiveModel::~ArchiveModel
~ArchiveModel()
Definition: archivemodel.cpp:289
QString::mid
QString mid(int position, int n) const
QMimeData::hasUrls
bool hasUrls() const
QVector
QTest::toString
char * toString(const T &value)
ArchiveModel::data
QVariant data(const QModelIndex &index, int role) const
Definition: archivemodel.cpp:295
QList::takeFirst
T takeFirst()
QLatin1String
Qt::DropActions
typedef DropActions
QMimeData::urls
QList< QUrl > urls() const
QAbstractItemModel::hasChildren
virtual bool hasChildren(const QModelIndex &parent) const
ArchiveModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const
Definition: archivemodel.cpp:485
Kerfuffle::Method
The compression method used on the entry.
Definition: archive.h:69
Kerfuffle::Size
The entry's original size.
Definition: archive.h:64
s_previousMatch
static ArchiveNode * s_previousMatch
Definition: archivemodel.cpp:48
QList::last
T & last()
ArchiveModel::supportedDropActions
virtual Qt::DropActions supportedDropActions() const
Definition: archivemodel.cpp:548
QList::removeLast
void removeLast()
QModelIndex::column
int column() const
QStringList::split
QStringList split(const QString &sep, const QString &str, bool allowEmptyEntries)
QAbstractItemModel
ArchiveModel::sort
virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder)
Definition: archivemodel.cpp:505
ArchiveModel::columnCount
int columnCount(const QModelIndex &parent=QModelIndex()) const
Definition: archivemodel.cpp:497
QAbstractItemModel::flags
virtual Qt::ItemFlags flags(const QModelIndex &index) const
QList::prepend
void prepend(const T &value)
ArchiveModel::extractFiles
Kerfuffle::ExtractJob * extractFiles(const QList< QVariant > &files, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
Definition: archivemodel.cpp:902
QMimeData::setData
void setData(const QString &mimeType, const QByteArray &data)
Kerfuffle::Comment
Definition: archive.h:73
Kerfuffle::Group
The user group the entry belongs to.
Definition: archive.h:63
Kerfuffle::InternalID
The entry's ID for Ark's internal manipulation.
Definition: archive.h:60
QAbstractItemModel::endRemoveRows
void endRemoveRows()
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::parent
QObject * parent() const
QAbstractItemModel::endInsertColumns
void endInsertColumns()
QVariant::toString
QString toString() const
QAbstractItemModel::beginInsertColumns
void beginInsertColumns(const QModelIndex &parent, int first, int last)
Kerfuffle::Query::execute
virtual void execute()=0
Execute the response.
KJob
ArchiveModel::loadingFinished
void loadingFinished(KJob *)
QDateTime
ArchiveModel::entryForIndex
Kerfuffle::ArchiveEntry entryForIndex(const QModelIndex &index)
Definition: archivemodel.cpp:453
QVariant
Qt::ItemFlags
typedef ItemFlags
QString::toUtf8
QByteArray toUtf8() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:42:36 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

ark

Skip menu "ark"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdeutils API Reference

Skip menu "kdeutils API Reference"
  • ark
  • filelight
  • kcalc
  • kcharselect
  • kdf
  • kfloppy
  • kgpg
  • ktimer
  • kwallet
  • sweeper

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