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"