File indexing completed on 2024-05-12 04:59:21

0001 /***************************************************************************
0002  *   Copyright (C) 2007 by Joris Guisson and Ivan Vasic                    *
0003  *   joris.guisson@gmail.com                                               *
0004  *   ivasic@gmail.com                                                      *
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  *   This program is distributed in the hope that it will be useful,       *
0012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *   GNU General Public License for more details.                          *
0015  *                                                                         *
0016  *   You should have received a copy of the GNU General Public License     *
0017  *   along with this program; if not, write to the                         *
0018  *   Free Software Foundation, Inc.,                                       *
0019  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
0020  ***************************************************************************/
0021 #include "torrentfiletreemodel.h"
0022 
0023 #include <KLocalizedString>
0024 
0025 #include <QIcon>
0026 #include <QMimeDatabase>
0027 #include <QMimeType>
0028 #include <QSortFilterProxyModel>
0029 #include <QTreeView>
0030 
0031 #include <bcodec/bdecoder.h>
0032 #include <bcodec/bencoder.h>
0033 #include <bcodec/bnode.h>
0034 #include <interfaces/torrentfileinterface.h>
0035 #include <interfaces/torrentinterface.h>
0036 #include <util/functions.h>
0037 #include <util/log.h>
0038 
0039 using namespace bt;
0040 
0041 namespace kt
0042 {
0043 
0044 TorrentFileTreeModel::Node::Node(Node *parent, bt::TorrentFileInterface *file, const QString &name, const bt::Uint32 total_chunks)
0045     : parent(parent)
0046     , file(file)
0047     , name(name)
0048     , size(0)
0049     , chunks(total_chunks)
0050     , chunks_set(false)
0051     , percentage(0.0f)
0052 {
0053     chunks.setAll(false);
0054 }
0055 
0056 TorrentFileTreeModel::Node::Node(Node *parent, const QString &name, const bt::Uint32 total_chunks)
0057     : parent(parent)
0058     , file(nullptr)
0059     , name(name)
0060     , size(0)
0061     , chunks(total_chunks)
0062     , chunks_set(false)
0063     , percentage(0.0f)
0064 {
0065     chunks.setAll(false);
0066 }
0067 
0068 TorrentFileTreeModel::Node::~Node()
0069 {
0070     qDeleteAll(children);
0071 }
0072 
0073 void TorrentFileTreeModel::Node::insert(const QString &path, bt::TorrentFileInterface *file, bt::Uint32 num_chunks)
0074 {
0075     int p = path.indexOf(bt::DirSeparator());
0076     if (p == -1) {
0077         // the file is part of this directory
0078         children.append(new Node(this, file, path, num_chunks));
0079     } else {
0080         QString subdir = path.left(p);
0081         foreach (Node *n, children) {
0082             if (n->name == subdir) {
0083                 n->insert(path.mid(p + 1), file, num_chunks);
0084                 return;
0085             }
0086         }
0087 
0088         Node *n = new Node(this, subdir, num_chunks);
0089         children.append(n);
0090         n->insert(path.mid(p + 1), file, num_chunks);
0091     }
0092 }
0093 
0094 int TorrentFileTreeModel::Node::row()
0095 {
0096     if (parent)
0097         return parent->children.indexOf(this);
0098     else
0099         return 0;
0100 }
0101 
0102 bt::Uint64 TorrentFileTreeModel::Node::fileSize(const bt::TorrentInterface *tc)
0103 {
0104     if (size > 0)
0105         return size;
0106 
0107     if (!file) {
0108         // directory
0109         foreach (Node *n, children)
0110             size += n->fileSize(tc);
0111     } else {
0112         size = file->getSize();
0113     }
0114     return size;
0115 }
0116 
0117 void TorrentFileTreeModel::Node::fillChunks()
0118 {
0119     if (chunks_set)
0120         return;
0121 
0122     if (!file) {
0123         foreach (Node *n, children) {
0124             n->fillChunks();
0125             chunks.orBitSet(n->chunks);
0126         }
0127     } else {
0128         for (Uint32 i = file->getFirstChunk(); i <= file->getLastChunk(); ++i)
0129             chunks.set(i, true);
0130     }
0131     chunks_set = true;
0132 }
0133 
0134 void TorrentFileTreeModel::Node::updatePercentage(const BitSet &havechunks)
0135 {
0136     if (!chunks_set)
0137         fillChunks(); // make sure we know the chunks which are part of this node
0138 
0139     if (file) {
0140         percentage = file->getDownloadPercentage();
0141     } else {
0142         if (havechunks.numOnBits() == 0 || chunks.numOnBits() == 0) {
0143             percentage = 0.0f;
0144         } else if (havechunks.allOn()) {
0145             percentage = 100.0f;
0146         } else {
0147             // take the chunks of the node and
0148             // logical and them with the chunks we have
0149             BitSet tmp(chunks);
0150             tmp.andBitSet(havechunks);
0151 
0152             percentage = 100.0f * ((float)tmp.numOnBits() / (float)chunks.numOnBits());
0153         }
0154     }
0155 
0156     if (parent)
0157         parent->updatePercentage(havechunks); // update the percentage of the parent
0158 }
0159 
0160 void TorrentFileTreeModel::Node::initPercentage(const bt::TorrentInterface *tc, const bt::BitSet &havechunks)
0161 {
0162     if (!chunks_set)
0163         fillChunks();
0164 
0165     if (!tc->getStats().multi_file_torrent) {
0166         percentage = bt::Percentage(tc->getStats());
0167         return;
0168     }
0169 
0170     if (file) {
0171         percentage = file->getDownloadPercentage();
0172     } else {
0173         if (havechunks.numOnBits() == 0 || chunks.numOnBits() == 0) {
0174             percentage = 0.0f;
0175         } else if (havechunks.allOn()) {
0176             percentage = 100.0f;
0177         } else {
0178             // take the chunks of the node and
0179             // logical and them with the chunks we have
0180             BitSet tmp(chunks);
0181             tmp.andBitSet(havechunks);
0182 
0183             percentage = 100.0f * ((float)tmp.numOnBits() / (float)chunks.numOnBits());
0184         }
0185 
0186         foreach (Node *n, children)
0187             n->initPercentage(tc, havechunks); // update the percentage of the children
0188     }
0189 }
0190 
0191 bt::Uint64 TorrentFileTreeModel::Node::bytesToDownload(const bt::TorrentInterface *tc)
0192 {
0193     bt::Uint64 s = 0;
0194 
0195     if (!file) {
0196         // directory
0197         foreach (Node *n, children)
0198             s += n->bytesToDownload(tc);
0199     } else {
0200         if (!file->doNotDownload())
0201             s = file->getSize();
0202     }
0203     return s;
0204 }
0205 
0206 Qt::CheckState TorrentFileTreeModel::Node::checkState(const bt::TorrentInterface *tc) const
0207 {
0208     if (!file) {
0209         bool found_checked = false;
0210         bool found_unchecked = false;
0211         // directory
0212         foreach (Node *n, children) {
0213             Qt::CheckState s = n->checkState(tc);
0214             if (s == Qt::PartiallyChecked)
0215                 return s;
0216             else if (s == Qt::Checked)
0217                 found_checked = true;
0218             else
0219                 found_unchecked = true;
0220 
0221             if (found_checked && found_unchecked)
0222                 return Qt::PartiallyChecked;
0223         }
0224 
0225         return found_checked ? Qt::Checked : Qt::Unchecked;
0226     } else {
0227         return file->doNotDownload() || file->getPriority() == ONLY_SEED_PRIORITY ? Qt::Unchecked : Qt::Checked;
0228     }
0229 }
0230 
0231 void TorrentFileTreeModel::Node::saveExpandedState(const QModelIndex &index, QSortFilterProxyModel *pm, QTreeView *tv, BEncoder *enc)
0232 {
0233     if (file)
0234         return;
0235 
0236     enc->write(QByteArray("expanded"));
0237     enc->write((Uint32)(tv->isExpanded(pm->mapFromSource(index)) ? 1 : 0));
0238 
0239     int idx = 0;
0240     foreach (Node *n, children) {
0241         if (!n->file) {
0242             enc->write(n->name.toUtf8());
0243             enc->beginDict();
0244             n->saveExpandedState(index.model()->index(idx, 0), pm, tv, enc);
0245             enc->end();
0246         }
0247         ++idx;
0248     }
0249 }
0250 
0251 void TorrentFileTreeModel::Node::loadExpandedState(const QModelIndex &index, QSortFilterProxyModel *pm, QTreeView *tv, BNode *n)
0252 {
0253     if (file)
0254         return;
0255 
0256     auto *dict = dynamic_cast<BDictNode *>(n);
0257     if (!dict)
0258         return;
0259 
0260     BValueNode *v = dict->getValue(QByteArray("expanded"));
0261     if (v)
0262         tv->setExpanded(pm->mapFromSource(index), v->data().toInt() == 1);
0263 
0264     int idx = 0;
0265     foreach (Node *n, children) {
0266         if (!n->file) {
0267             BDictNode *d = dict->getDict(n->name.toUtf8());
0268             if (d)
0269                 n->loadExpandedState(index.model()->index(idx, 0), pm, tv, d);
0270         }
0271         idx++;
0272     }
0273 }
0274 
0275 QString TorrentFileTreeModel::Node::path()
0276 {
0277     if (!parent)
0278         return QString(); // the root node must not be included in the path
0279 
0280     if (file)
0281         return name;
0282     else
0283         return parent->path() + name + bt::DirSeparator();
0284 }
0285 
0286 TorrentFileTreeModel::TorrentFileTreeModel(bt::TorrentInterface *tc, DeselectMode mode, QObject *parent)
0287     : TorrentFileModel(tc, mode, parent)
0288     , root(nullptr)
0289     , emit_check_state_change(true)
0290 {
0291     if (tc->getStats().multi_file_torrent)
0292         constructTree();
0293     else
0294         root = new Node(nullptr, tc->getStats().torrent_name, tc->getStats().total_chunks);
0295 }
0296 
0297 TorrentFileTreeModel::~TorrentFileTreeModel()
0298 {
0299     delete root;
0300 }
0301 
0302 void TorrentFileTreeModel::constructTree()
0303 {
0304     bt::Uint32 num_chunks = tc->getStats().total_chunks;
0305     if (!root)
0306         root = new Node(nullptr, tc->getUserModifiedFileName(), num_chunks);
0307 
0308     for (Uint32 i = 0; i < tc->getNumFiles(); ++i) {
0309         bt::TorrentFileInterface &tf = tc->getTorrentFile(i);
0310         root->insert(tf.getUserModifiedPath(), &tf, num_chunks);
0311     }
0312 }
0313 
0314 void TorrentFileTreeModel::onCodecChange()
0315 {
0316     beginResetModel();
0317     delete root;
0318     root = nullptr;
0319     constructTree();
0320     endResetModel();
0321 }
0322 
0323 int TorrentFileTreeModel::rowCount(const QModelIndex &parent) const
0324 {
0325     if (!parent.isValid()) {
0326         return 1;
0327     } else {
0328         Node *n = (Node *)parent.internalPointer();
0329         return n->children.count();
0330     }
0331 }
0332 
0333 int TorrentFileTreeModel::columnCount(const QModelIndex &parent) const
0334 {
0335     if (!parent.isValid())
0336         return 2;
0337     else
0338         return 2;
0339 }
0340 
0341 QVariant TorrentFileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0342 {
0343     if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
0344         return QVariant();
0345 
0346     switch (section) {
0347     case 0:
0348         return i18n("File");
0349     case 1:
0350         return i18n("Size");
0351     default:
0352         return QVariant();
0353     }
0354 }
0355 
0356 QVariant TorrentFileTreeModel::data(const QModelIndex &index, int role) const
0357 {
0358     if (!index.isValid())
0359         return QVariant();
0360 
0361     Node *n = (Node *)index.internalPointer();
0362     if (!n)
0363         return QVariant();
0364 
0365     if (role == Qt::DisplayRole || role == Qt::EditRole) {
0366         switch (index.column()) {
0367         case 0:
0368             return n->name;
0369         case 1:
0370             if (tc->getStats().multi_file_torrent)
0371                 return BytesToString(n->fileSize(tc));
0372             else
0373                 return BytesToString(tc->getStats().total_bytes);
0374         default:
0375             return QVariant();
0376         }
0377     } else if (role == Qt::UserRole) // sorting
0378     {
0379         switch (index.column()) {
0380         case 0:
0381             return n->name;
0382         case 1:
0383             if (tc->getStats().multi_file_torrent)
0384                 return n->fileSize(tc);
0385             else
0386                 return tc->getStats().total_bytes;
0387         default:
0388             return QVariant();
0389         }
0390     } else if (role == Qt::DecorationRole && index.column() == 0) {
0391         // if this is an empty folder then we are in the single file case
0392         QMimeDatabase db;
0393         if (!n->file)
0394             return n->children.count() > 0 ? QIcon::fromTheme("folder") : QIcon::fromTheme(db.mimeTypeForFile(tc->getStats().torrent_name).iconName());
0395         else
0396             return QIcon::fromTheme(db.mimeTypeForFile(n->file->getPath()).iconName());
0397     } else if (role == Qt::CheckStateRole && index.column() == 0) {
0398         if (tc->getStats().multi_file_torrent)
0399             return n->checkState(tc);
0400     }
0401 
0402     return QVariant();
0403 }
0404 
0405 QModelIndex TorrentFileTreeModel::parent(const QModelIndex &index) const
0406 {
0407     if (!index.isValid())
0408         return QModelIndex();
0409 
0410     Node *child = static_cast<Node *>(index.internalPointer());
0411     if (!child)
0412         return QModelIndex();
0413 
0414     Node *parent = child->parent;
0415     if (!parent)
0416         return QModelIndex();
0417     else
0418         return createIndex(parent->row(), 0, parent);
0419 }
0420 
0421 QModelIndex TorrentFileTreeModel::index(int row, int column, const QModelIndex &parent) const
0422 {
0423     if (!hasIndex(row, column, parent))
0424         return QModelIndex();
0425 
0426     Node *p = nullptr;
0427 
0428     if (!parent.isValid())
0429         return createIndex(row, column, root);
0430     else {
0431         p = static_cast<Node *>(parent.internalPointer());
0432 
0433         if (row >= 0 && row < p->children.count())
0434             return createIndex(row, column, p->children.at(row));
0435         else
0436             return QModelIndex();
0437     }
0438 }
0439 
0440 bool TorrentFileTreeModel::setCheckState(const QModelIndex &index, Qt::CheckState state)
0441 {
0442     Node *n = static_cast<Node *>(index.internalPointer());
0443     if (!n)
0444         return false;
0445 
0446     if (!n->file) {
0447         bool reenable = false;
0448         if (emit_check_state_change) {
0449             reenable = true;
0450             emit_check_state_change = false;
0451         }
0452 
0453         for (int i = 0; i < n->children.count(); i++) {
0454             // recurse down the tree
0455             setCheckState(index.model()->index(i, 0), state);
0456         }
0457 
0458         if (reenable)
0459             emit_check_state_change = true;
0460     } else {
0461         bt::TorrentFileInterface *file = n->file;
0462         if (state == Qt::Checked) {
0463             if (file->getPriority() == ONLY_SEED_PRIORITY)
0464                 file->setPriority(NORMAL_PRIORITY);
0465             else
0466                 file->setDoNotDownload(false);
0467         } else {
0468             if (mode == KEEP_FILES)
0469                 file->setPriority(ONLY_SEED_PRIORITY);
0470             else
0471                 file->setDoNotDownload(true);
0472         }
0473         dataChanged(createIndex(index.row(), 0), createIndex(index.row(), columnCount(index) - 1));
0474 
0475         QModelIndex parent = index.parent();
0476         if (parent.isValid())
0477             dataChanged(parent, parent); // parent needs to be updated to
0478     }
0479 
0480     if (emit_check_state_change)
0481         checkStateChanged();
0482     return true;
0483 }
0484 
0485 void TorrentFileTreeModel::modifyPathOfFiles(Node *n, const QString &path)
0486 {
0487     for (int i = 0; i < n->children.count(); i++) {
0488         Node *c = n->children.at(i);
0489         if (!c->file) // another directory, continue recursively
0490             modifyPathOfFiles(c, path + c->name + bt::DirSeparator());
0491         else
0492             c->file->setUserModifiedPath(path + c->name);
0493     }
0494 }
0495 
0496 bool TorrentFileTreeModel::setName(const QModelIndex &index, const QString &name)
0497 {
0498     Node *n = static_cast<Node *>(index.internalPointer());
0499     if (!n || name.isEmpty() || name.contains(bt::DirSeparator()))
0500         return false;
0501 
0502     if (!tc->getStats().multi_file_torrent) {
0503         // single file case so we only need to change the user modified name
0504         tc->setUserModifiedFileName(name);
0505         n->name = name;
0506         dataChanged(index, index);
0507         return true;
0508     }
0509 
0510     if (!n->file) {
0511         // we are in a directory
0512         n->name = name;
0513         if (!n->parent) {
0514             // toplevel directory name has changed
0515             tc->setUserModifiedFileName(name);
0516         }
0517 
0518         dataChanged(index, index);
0519         // modify the path of all files
0520         modifyPathOfFiles(n, n->path());
0521         return true;
0522     } else {
0523         n->name = name;
0524         n->file->setUserModifiedPath(n->path());
0525         dataChanged(index, index);
0526         return true;
0527     }
0528 }
0529 
0530 bool TorrentFileTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
0531 {
0532     if (!index.isValid())
0533         return false;
0534 
0535     if (role == Qt::CheckStateRole)
0536         return setCheckState(index, static_cast<Qt::CheckState>(value.toInt()));
0537     else if (role == Qt::EditRole)
0538         return setName(index, value.toString());
0539 
0540     return false;
0541 }
0542 
0543 void TorrentFileTreeModel::checkAll()
0544 {
0545     if (tc->getStats().multi_file_torrent)
0546         setData(index(0, 0, QModelIndex()), Qt::Checked, Qt::CheckStateRole);
0547 }
0548 
0549 void TorrentFileTreeModel::uncheckAll()
0550 {
0551     if (tc->getStats().multi_file_torrent)
0552         setData(index(0, 0, QModelIndex()), Qt::Unchecked, Qt::CheckStateRole);
0553 }
0554 
0555 void TorrentFileTreeModel::invertCheck()
0556 {
0557     if (!tc->getStats().multi_file_torrent)
0558         return;
0559 
0560     invertCheck(index(0, 0, QModelIndex()));
0561 }
0562 
0563 void TorrentFileTreeModel::invertCheck(const QModelIndex &idx)
0564 {
0565     Node *n = static_cast<Node *>(idx.internalPointer());
0566     if (!n)
0567         return;
0568 
0569     if (!n->file) {
0570         for (int i = 0; i < n->children.count(); i++) {
0571             // recurse down the tree
0572             invertCheck(idx.model()->index(i, 0));
0573         }
0574     } else {
0575         if (n->file->doNotDownload())
0576             setData(idx, Qt::Checked, Qt::CheckStateRole);
0577         else
0578             setData(idx, Qt::Unchecked, Qt::CheckStateRole);
0579     }
0580 }
0581 
0582 bt::Uint64 TorrentFileTreeModel::bytesToDownload()
0583 {
0584     if (tc->getStats().multi_file_torrent)
0585         return root->bytesToDownload(tc);
0586     else
0587         return tc->getStats().total_bytes;
0588 }
0589 
0590 QByteArray TorrentFileTreeModel::saveExpandedState(QSortFilterProxyModel *pm, QTreeView *tv)
0591 {
0592     if (!tc->getStats().multi_file_torrent)
0593         return QByteArray();
0594 
0595     QByteArray data;
0596     BEncoder enc(new BEncoderBufferOutput(data));
0597     enc.beginDict();
0598     root->saveExpandedState(index(0, 0, QModelIndex()), pm, tv, &enc);
0599     enc.end();
0600     return data;
0601 }
0602 
0603 void TorrentFileTreeModel::loadExpandedState(QSortFilterProxyModel *pm, QTreeView *tv, const QByteArray &state)
0604 {
0605     if (!tc->getStats().multi_file_torrent)
0606         return;
0607 
0608     BDecoder dec(state, false, 0);
0609     BNode *n = dec.decode();
0610     if (n && n->getType() == BNode::DICT) {
0611         root->loadExpandedState(index(0, 0, QModelIndex()), pm, tv, n);
0612     }
0613     delete n;
0614 }
0615 
0616 bt::TorrentFileInterface *TorrentFileTreeModel::indexToFile(const QModelIndex &idx)
0617 {
0618     if (!idx.isValid())
0619         return nullptr;
0620 
0621     Node *n = (Node *)idx.internalPointer();
0622     if (!n)
0623         return nullptr;
0624 
0625     return n->file;
0626 }
0627 
0628 QString TorrentFileTreeModel::dirPath(const QModelIndex &idx)
0629 {
0630     if (!idx.isValid())
0631         return QString();
0632 
0633     Node *n = (Node *)idx.internalPointer();
0634     if (!n || n == root)
0635         return QString();
0636 
0637     QString ret = n->name;
0638     do {
0639         n = n->parent;
0640         if (n && n->parent)
0641             ret = n->name + bt::DirSeparator() + ret;
0642     } while (n);
0643 
0644     return ret;
0645 }
0646 
0647 void TorrentFileTreeModel::changePriority(const QModelIndexList &indexes, bt::Priority newpriority)
0648 {
0649     foreach (const QModelIndex &idx, indexes) {
0650         Node *n = (Node *)idx.internalPointer();
0651         if (!n)
0652             continue;
0653 
0654         setData(idx, newpriority, Qt::UserRole);
0655     }
0656 }
0657 }
0658 
0659 #include "moc_torrentfiletreemodel.cpp"