File indexing completed on 2024-12-01 07:38:39

0001 /***************************************************************************
0002  *   Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>                     *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; if not, write to the                         *
0016  *   Free Software Foundation, Inc.,                                       *
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0018  ***************************************************************************/
0019 
0020 #include "filemodel.h"
0021 
0022 #include "signature.h"
0023 #include "verifier.h"
0024 
0025 #include <KIO/Global>
0026 #include <KLocalizedString>
0027 
0028 FileItem::FileItem(const QString &name, FileItem *parent)
0029     : m_name(name)
0030     , m_state(Qt::Checked)
0031     , m_status(Job::Stopped)
0032     , m_totalSize(0)
0033     , m_checkusmVerified(0)
0034     , m_signatureVerified(0)
0035     , m_parent(parent)
0036 {
0037 }
0038 
0039 FileItem::~FileItem()
0040 {
0041     qDeleteAll(m_childItems);
0042 }
0043 
0044 void FileItem::appendChild(FileItem *child)
0045 {
0046     m_childItems.append(child);
0047 }
0048 
0049 FileItem *FileItem::child(int row)
0050 {
0051     return m_childItems.value(row);
0052 }
0053 
0054 int FileItem::childCount() const
0055 {
0056     return m_childItems.count();
0057 }
0058 
0059 bool FileItem::isFile() const
0060 {
0061     return m_childItems.isEmpty();
0062 }
0063 
0064 int FileItem::columnCount() const
0065 {
0066     return 5;
0067 }
0068 
0069 QVariant FileItem::data(int column, int role) const
0070 {
0071     if (column == FileItem::File) {
0072         if (role == Qt::CheckStateRole) {
0073             return m_state;
0074         } else if (role == Qt::DisplayRole) {
0075             return m_name;
0076         } else if (role == Qt::DecorationRole) {
0077             if (m_mimeType.isNull()) {
0078                 if (isFile()) {
0079                     m_mimeType = QIcon::fromTheme(KIO::iconNameForUrl(QUrl(m_name)));
0080                 } else {
0081                     m_mimeType = QIcon::fromTheme("folder");
0082                 }
0083             }
0084 
0085             return m_mimeType;
0086         }
0087     } else if (column == FileItem::Status) {
0088         if ((role == Qt::DisplayRole) || (role == Qt::DecorationRole)) {
0089             if (isFile()) {
0090                 return m_status;
0091             }
0092         }
0093     } else if (column == FileItem::Size) {
0094         if (role == Qt::DisplayRole) {
0095             return KIO::convertSize(m_totalSize);
0096         }
0097     } else if (column == FileItem::ChecksumVerified) {
0098         if (role == Qt::DecorationRole) {
0099             switch (m_checkusmVerified) {
0100             case Verifier::Verified:
0101                 return QIcon::fromTheme("dialog-ok");
0102             case Verifier::NotVerified:
0103                 return QIcon::fromTheme("dialog-error");
0104             case Verifier::NoResult:
0105             default:
0106                 return QIcon::fromTheme(QString());
0107             }
0108         }
0109     } else if (column == FileItem::SignatureVerified) { // TODO implement all cases
0110         if (role == Qt::DecorationRole) {
0111             switch (m_signatureVerified) {
0112             case Signature::Verified:
0113                 return QIcon::fromTheme("dialog-ok");
0114             case Signature::VerifiedInformation:
0115                 return QIcon::fromTheme("dialog-information");
0116             case Signature::VerifiedWarning:
0117                 return QIcon::fromTheme("dialog-warning");
0118             case Signature::NotVerified:
0119                 return QIcon::fromTheme("dialog-error");
0120             case Signature::NoResult:
0121             default:
0122                 return QIcon::fromTheme(QString());
0123             }
0124         }
0125     }
0126 
0127     return QVariant();
0128 }
0129 
0130 bool FileItem::setData(int column, const QVariant &value, FileModel *model, int role)
0131 {
0132     if (value.isNull()) {
0133         return false;
0134     }
0135 
0136     if (column == FileItem::File) {
0137         if (role == Qt::CheckStateRole) {
0138             m_state = static_cast<Qt::CheckState>(value.toInt());
0139             model->changeData(this->row(), column, this);
0140             checkParents(m_state, model);
0141             checkChildren(m_state, model);
0142             return true;
0143         } else if (role == Qt::EditRole) {
0144             m_name = value.toString();
0145             model->changeData(this->row(), column, this);
0146             return true;
0147         }
0148     } else if (column == FileItem::Status) {
0149         if (role == Qt::EditRole) {
0150             if (isFile()) {
0151                 m_status = static_cast<Job::Status>(value.toInt());
0152                 bool finished = (m_status == Job::Finished);
0153                 model->changeData(this->row(), column, this, finished);
0154 
0155                 return true;
0156             }
0157         }
0158     } else if (column == FileItem::Size) {
0159         if (role == Qt::EditRole) {
0160             KIO::fileoffset_t newSize = value.toLongLong();
0161             if (m_parent) {
0162                 m_parent->addSize(newSize - m_totalSize, model);
0163             }
0164             m_totalSize = newSize;
0165             model->changeData(this->row(), column, this);
0166             return true;
0167         }
0168     } else if (column == FileItem::ChecksumVerified) {
0169         m_checkusmVerified = value.toInt();
0170         model->changeData(this->row(), column, this);
0171         return true;
0172     } else if (column == FileItem::SignatureVerified) {
0173         m_signatureVerified = value.toInt();
0174         model->changeData(this->row(), column, this);
0175         return true;
0176     }
0177 
0178     return false;
0179 }
0180 
0181 void FileItem::checkParents(Qt::CheckState state, FileModel *model)
0182 {
0183     if (!model) {
0184         return;
0185     }
0186 
0187     if (!m_parent) {
0188         return;
0189     }
0190 
0191     foreach (FileItem *child, m_parent->m_childItems) {
0192         if (child->m_state != state) {
0193             state = Qt::Unchecked;
0194             break;
0195         }
0196     }
0197 
0198     m_parent->m_state = state;
0199     model->changeData(m_parent->row(), FileItem::File, m_parent);
0200     m_parent->checkParents(state, model);
0201 }
0202 
0203 void FileItem::checkChildren(Qt::CheckState state, FileModel *model)
0204 {
0205     if (!model) {
0206         return;
0207     }
0208 
0209     m_state = state;
0210     model->changeData(row(), FileItem::File, this);
0211 
0212     foreach (FileItem *child, m_childItems) {
0213         child->checkChildren(state, model);
0214     }
0215 }
0216 
0217 FileItem *FileItem::parent()
0218 {
0219     return m_parent;
0220 }
0221 
0222 int FileItem::row() const
0223 {
0224     if (m_parent) {
0225         return m_parent->m_childItems.indexOf(const_cast<FileItem *>(this));
0226     }
0227 
0228     return 0;
0229 }
0230 
0231 void FileItem::addSize(KIO::fileoffset_t size, FileModel *model)
0232 {
0233     if (!isFile()) {
0234         m_totalSize += size;
0235         model->changeData(this->row(), FileItem::Size, this);
0236         if (m_parent) {
0237             m_parent->addSize(size, model);
0238         }
0239     }
0240 }
0241 
0242 FileModel::FileModel(const QList<QUrl> &files, const QUrl &destDirectory, QObject *parent)
0243     : QAbstractItemModel(parent)
0244     , m_destDirectory(destDirectory)
0245     , m_checkStateChanged(false)
0246 {
0247     m_rootItem = new FileItem("root");
0248     m_header << i18nc("file in a filesystem", "File") << i18nc("status of the download", "Status") << i18nc("size of the download", "Size")
0249              << i18nc("checksum of a file", "Checksum") << i18nc("signature of a file", "Signature");
0250 
0251     setupModelData(files);
0252 }
0253 
0254 FileModel::~FileModel()
0255 {
0256     delete m_rootItem;
0257 }
0258 
0259 void FileModel::setupModelData(const QList<QUrl> &files)
0260 {
0261     QString destDirectory = m_destDirectory.toLocalFile();
0262 
0263     foreach (const QUrl &file, files) {
0264         FileItem *parent = m_rootItem;
0265         QStringList directories = file.toLocalFile().remove(destDirectory).split('/', Qt::SkipEmptyParts);
0266         FileItem *child = nullptr;
0267         while (directories.count()) {
0268             QString part = directories.takeFirst();
0269             for (int i = 0; i < parent->childCount(); ++i) {
0270                 // folder already exists
0271                 if (parent->child(i)->data(0, Qt::DisplayRole).toString() == part) {
0272                     parent = parent->child(i);
0273                     // file already exists
0274                     if (!directories.count()) {
0275                         break;
0276                     }
0277                     part = directories.takeFirst();
0278                     i = -1;
0279                     continue;
0280                 }
0281             }
0282             child = new FileItem(part, parent);
0283             parent->appendChild(child);
0284             parent = parent->child(parent->childCount() - 1);
0285         }
0286         if (child) {
0287             m_files.append(child);
0288         }
0289     }
0290 }
0291 
0292 int FileModel::columnCount(const QModelIndex &parent) const
0293 {
0294     if (parent.isValid()) {
0295         return static_cast<FileItem *>(parent.internalPointer())->columnCount();
0296     } else {
0297         return m_rootItem->columnCount();
0298     }
0299 }
0300 
0301 QVariant FileModel::data(const QModelIndex &index, int role) const
0302 {
0303     if (!index.isValid()) {
0304         return QVariant();
0305     }
0306 
0307     auto *item = static_cast<FileItem *>(index.internalPointer());
0308     const QVariant data = item->data(index.column(), role);
0309 
0310     // get the status icon as well as status text
0311     if (index.column() == FileItem::Status) {
0312         const auto status = static_cast<Job::Status>(data.toInt());
0313         if (item->isFile()) {
0314             if (role == Qt::DisplayRole) {
0315                 if (m_customStatusTexts.contains(status)) {
0316                     return m_customStatusTexts[status];
0317                 } else {
0318                     return Transfer::statusText(status);
0319                 }
0320             } else if (role == Qt::DecorationRole) {
0321                 if (m_customStatusIcons.contains(status)) {
0322                     return m_customStatusIcons[status];
0323                 } else {
0324                     return QIcon::fromTheme(Transfer::statusIconName(status));
0325                 }
0326             }
0327         } else {
0328             return QVariant();
0329         }
0330     }
0331 
0332     return data;
0333 }
0334 
0335 bool FileModel::setData(const QModelIndex &index, const QVariant &value, int role)
0336 {
0337     if (!index.isValid()) {
0338         return false;
0339     }
0340 
0341     auto *item = static_cast<FileItem *>(index.internalPointer());
0342 
0343     if ((index.column() == FileItem::File) && (role == Qt::CheckStateRole)) {
0344         const bool worked = item->setData(index.column(), value, this, role);
0345         if (worked) {
0346             m_checkStateChanged = true;
0347         }
0348 
0349         return worked;
0350     }
0351 
0352     return item->setData(index.column(), value, this, role);
0353 }
0354 
0355 Qt::ItemFlags FileModel::flags(const QModelIndex &index) const
0356 {
0357     if (!index.isValid()) {
0358         return Qt::NoItemFlags;
0359     }
0360 
0361     if (index.column() == FileItem::File) {
0362         return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable;
0363     }
0364 
0365     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0366 }
0367 
0368 QVariant FileModel::headerData(int section, Qt::Orientation orientation, int role) const
0369 {
0370     if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) {
0371         return m_header.value(section);
0372     }
0373 
0374     return QVariant();
0375 }
0376 
0377 QModelIndex FileModel::index(int row, int column, const QModelIndex &parent) const
0378 {
0379     if (!hasIndex(row, column, parent)) {
0380         return QModelIndex();
0381     }
0382 
0383     FileItem *parentItem;
0384     if (parent.isValid()) {
0385         parentItem = static_cast<FileItem *>(parent.internalPointer());
0386     } else {
0387         parentItem = m_rootItem;
0388     }
0389 
0390     FileItem *childItem = parentItem->child(row);
0391     if (childItem) {
0392         return createIndex(row, column, childItem);
0393     } else {
0394         return QModelIndex();
0395     }
0396 }
0397 
0398 QModelIndex FileModel::index(const QUrl &file, int column)
0399 {
0400     FileItem *item = getItem(file);
0401     if (!item) {
0402         return QModelIndex();
0403     }
0404 
0405     return createIndex(item->row(), column, item);
0406 }
0407 
0408 QModelIndexList FileModel::fileIndexes(int column) const
0409 {
0410     QModelIndexList indexList;
0411     foreach (FileItem *item, m_files) {
0412         int row = item->row();
0413         indexList.append(createIndex(row, column, item));
0414     }
0415 
0416     return indexList;
0417 }
0418 
0419 QModelIndex FileModel::parent(const QModelIndex &index) const
0420 {
0421     if (!index.isValid()) {
0422         return QModelIndex();
0423     }
0424 
0425     auto *childItem = static_cast<FileItem *>(index.internalPointer());
0426     FileItem *parentItem = childItem->parent();
0427     if ((parentItem == m_rootItem) || (!parentItem)) {
0428         return QModelIndex();
0429     } else {
0430         return createIndex(parentItem->row(), 0, parentItem);
0431     }
0432 }
0433 
0434 int FileModel::rowCount(const QModelIndex &parent) const
0435 {
0436     if (parent.column() > 0) {
0437         return 0;
0438     }
0439 
0440     FileItem *parentItem;
0441     if (parent.isValid()) {
0442         parentItem = static_cast<FileItem *>(parent.internalPointer());
0443     } else {
0444         parentItem = m_rootItem;
0445     }
0446 
0447     return parentItem->childCount();
0448 }
0449 
0450 void FileModel::changeData(int row, int column, FileItem *item, bool finished)
0451 {
0452     QModelIndex index = createIndex(row, column, item);
0453     Q_EMIT dataChanged(index, index);
0454 
0455     if (finished) {
0456         const QUrl file = getUrl(index);
0457         Q_EMIT fileFinished(file);
0458     }
0459 }
0460 
0461 void FileModel::setDirectory(const QUrl &newDirectory)
0462 {
0463     m_destDirectory = newDirectory;
0464     m_itemCache.clear();
0465 }
0466 
0467 QUrl FileModel::getUrl(const QModelIndex &index)
0468 {
0469     if (!index.isValid()) {
0470         return QUrl();
0471     }
0472 
0473     const QModelIndex file = index.sibling(index.row(), FileItem::File);
0474 
0475     return getUrl(static_cast<FileItem *>(file.internalPointer()));
0476 }
0477 
0478 QUrl FileModel::getUrl(FileItem *item)
0479 {
0480     const QString path = getPath(item);
0481     const QString name = item->data(FileItem::File, Qt::DisplayRole).toString();
0482     QUrl url = m_destDirectory;
0483     url.setPath(m_destDirectory.path() + path + name);
0484 
0485     return url;
0486 }
0487 
0488 QString FileModel::getPath(FileItem *item)
0489 {
0490     FileItem *parent = item->parent();
0491     QString path;
0492     while (parent && parent->parent()) {
0493         path = parent->data(FileItem::File, Qt::DisplayRole).toString() + '/' + path;
0494         parent = parent->parent();
0495     }
0496 
0497     return path;
0498 }
0499 
0500 FileItem *FileModel::getItem(const QUrl &file)
0501 {
0502     if (m_itemCache.contains(file)) {
0503         return m_itemCache[file];
0504     }
0505 
0506     QString destDirectory = m_destDirectory.toLocalFile();
0507 
0508     FileItem *item = m_rootItem;
0509     QStringList directories = file.toLocalFile().remove(destDirectory).split('/', Qt::SkipEmptyParts);
0510     while (directories.count()) {
0511         QString part = directories.takeFirst();
0512         for (int i = 0; i < item->childCount(); ++i) {
0513             // folder already exists
0514             if (item->child(i)->data(FileItem::File, Qt::DisplayRole).toString() == part) {
0515                 item = item->child(i);
0516                 // file already exists
0517                 if (!directories.count()) {
0518                     break;
0519                 }
0520                 part = directories.takeFirst();
0521                 i = -1;
0522                 continue;
0523             }
0524         }
0525     }
0526 
0527     if (item == m_rootItem) {
0528         item = nullptr;
0529     } else {
0530         m_itemCache[file] = item;
0531     }
0532 
0533     return item;
0534 }
0535 
0536 bool FileModel::downloadFinished(const QUrl &file)
0537 {
0538     FileItem *item = getItem(file);
0539     if (item) {
0540         const Job::Status status = static_cast<Job::Status>(item->data(FileItem::Status, Qt::DisplayRole).toInt());
0541         if (status == Job::Finished) {
0542             return true;
0543         }
0544     }
0545 
0546     return false;
0547 }
0548 
0549 bool FileModel::isFile(const QModelIndex &index) const
0550 {
0551     if (!index.isValid()) {
0552         return false;
0553     }
0554 
0555     auto *item = static_cast<FileItem *>(index.internalPointer());
0556 
0557     // only files can be renamed, no folders
0558     return item->isFile();
0559 }
0560 
0561 void FileModel::rename(const QModelIndex &file, const QString &newName)
0562 {
0563     if (!file.isValid() || (file.column() != FileItem::File)) {
0564         return;
0565     }
0566 
0567     auto *item = static_cast<FileItem *>(file.internalPointer());
0568     // only files can be renamed, no folders
0569     if (!item->isFile()) {
0570         return;
0571     }
0572 
0573     // Find out the old and the new QUrl
0574     QString oldName = file.data(Qt::DisplayRole).toString();
0575     QString path = getPath(item);
0576 
0577     QUrl oldUrl = m_destDirectory;
0578     oldUrl.setPath(m_destDirectory.path() + path + oldName);
0579     QUrl newUrl = m_destDirectory;
0580     newUrl.setPath(m_destDirectory.path() + path + newName);
0581 
0582     m_itemCache.remove(oldUrl);
0583 
0584     setData(file, newName);
0585 
0586     Q_EMIT rename(oldUrl, newUrl);
0587 }
0588 
0589 void FileModel::renameFailed(const QUrl &beforeRename, const QUrl &afterRename)
0590 {
0591     Q_UNUSED(beforeRename)
0592     Q_UNUSED(afterRename)
0593 }
0594 
0595 void FileModel::watchCheckState()
0596 {
0597     m_checkStateChanged = false;
0598 }
0599 
0600 void FileModel::stopWatchCheckState()
0601 {
0602     if (m_checkStateChanged) {
0603         Q_EMIT checkStateChanged();
0604     }
0605 
0606     m_checkStateChanged = false;
0607 }
0608 
0609 #include "moc_filemodel.cpp"