From e619b6977a04e1f8ebabbdefc76ee9189372a31e Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Mon, 17 Aug 2009 05:14:03 +0000 Subject: [PATCH] - Added Magnet URI support (might be still buggy) * Known problem: Always added in paused state for some obscure reason) --- Changelog | 1 + src/FinishedTorrents.cpp | 12 + src/FinishedTorrents.h | 1 + src/GUI.cpp | 30 ++- src/arborescence.h | 382 ++++++++++++++++---------------- src/bittorrent.cpp | 166 +++++++++++--- src/bittorrent.h | 3 +- src/downloadingTorrents.cpp | 302 +++++++++++++------------ src/downloadingTorrents.h | 1 + src/misc.h | 423 +++++++++++++++++++----------------- src/properties_imp.cpp | 76 ++++--- src/qtorrenthandle.cpp | 77 +++---- src/torrentPersistentData.h | 24 +- 13 files changed, 848 insertions(+), 650 deletions(-) diff --git a/Changelog b/Changelog index 886042db9..5ae59cebd 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,5 @@ * Unknown - Christophe Dumez - v1.5.0 + - FEATURE: Added Magnet URI support - BUGFIX: torrent resume code rewrited * Thu Aug 13 2009 - Christophe Dumez - v1.4.0 diff --git a/src/FinishedTorrents.cpp b/src/FinishedTorrents.cpp index 6a9868a27..d9513b89c 100644 --- a/src/FinishedTorrents.cpp +++ b/src/FinishedTorrents.cpp @@ -64,6 +64,8 @@ FinishedTorrents::FinishedTorrents(QObject *parent, bittorrent *BTSession) : par if(!loadColWidthFinishedList()){ finishedList->header()->resizeSection(0, 200); } + // Connect BTSession signals + connect(BTSession, SIGNAL(metadataReceived(QTorrentHandle&)), this, SLOT(updateMetadata(QTorrentHandle&))); // Make download list header clickable for sorting finishedList->header()->setClickable(true); finishedList->header()->setSortIndicatorShown(true); @@ -266,6 +268,16 @@ void FinishedTorrents::on_actionSet_upload_limit_triggered(){ new BandwidthAllocationDialog(this, true, BTSession, hashes); } +void FinishedTorrents::updateMetadata(QTorrentHandle &h) { + QString hash = h.hash(); + int row = getRowFromHash(hash); + if(row != -1) { + qDebug("Updating torrent metadata in download list"); + finishedListModel->setData(finishedListModel->index(row, F_NAME), QVariant(h.name())); + finishedListModel->setData(finishedListModel->index(row, F_SIZE), QVariant((qlonglong)h.actual_size())); + } +} + void FinishedTorrents::updateTorrent(QTorrentHandle h) { QString hash = h.hash(); int row = getRowFromHash(hash); diff --git a/src/FinishedTorrents.h b/src/FinishedTorrents.h index 80e84eaf5..8dae37e5d 100644 --- a/src/FinishedTorrents.h +++ b/src/FinishedTorrents.h @@ -93,6 +93,7 @@ class FinishedTorrents : public QWidget, public Ui::seeding { void deleteTorrent(QString hash); void showPropertiesFromHash(QString hash); void loadLastSortedColumn(); + void updateMetadata(QTorrentHandle &h); signals: void torrentMovedFromFinishedList(QString); diff --git a/src/GUI.cpp b/src/GUI.cpp index 7a9164d3e..752b3b43c 100644 --- a/src/GUI.cpp +++ b/src/GUI.cpp @@ -763,6 +763,11 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis BTSession->downloadFromUrl(file); continue; } + if(file.startsWith("magnet:", Qt::CaseInsensitive)) { + // FIXME: Possibly skipped torrent addition dialog + BTSession->addMagnetUri(file); + continue; + } if(useTorrentAdditionDialog) { torrentAdditionDialog *dialog = new torrentAdditionDialog(this, BTSession); dialog->showLoad(file); @@ -921,11 +926,16 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis if(param.startsWith(QString::fromUtf8("http://"), Qt::CaseInsensitive) || param.startsWith(QString::fromUtf8("ftp://"), Qt::CaseInsensitive) || param.startsWith(QString::fromUtf8("https://"), Qt::CaseInsensitive)) { BTSession->downloadFromUrl(param); }else{ - if(useTorrentAdditionDialog) { - torrentAdditionDialog *dialog = new torrentAdditionDialog(this, BTSession); - dialog->showLoad(param); - }else{ - BTSession->addTorrent(param); + if(param.startsWith("magnet:", Qt::CaseInsensitive)) { + // FIXME: Possibily skipped torrent addition dialog + BTSession->addMagnetUri(param); + } else { + if(useTorrentAdditionDialog) { + torrentAdditionDialog *dialog = new torrentAdditionDialog(this, BTSession); + dialog->showLoad(param); + }else{ + BTSession->addTorrent(param); + } } } } @@ -1491,8 +1501,14 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis * * *****************************************************/ - void GUI::downloadFromURLList(const QStringList& urls) { - BTSession->downloadFromURLList(urls); + void GUI::downloadFromURLList(const QStringList& url_list) { + foreach(const QString url, url_list) { + if(url.startsWith("magnet:", Qt::CaseInsensitive)) { + BTSession->addMagnetUri(url); + } else { + BTSession->downloadFromUrl(url); + } + } } /***************************************************** diff --git a/src/arborescence.h b/src/arborescence.h index 6bcbdfabb..5b6ff0f87 100644 --- a/src/arborescence.h +++ b/src/arborescence.h @@ -37,234 +37,234 @@ #include "misc.h" class torrent_file { - private: - torrent_file *parent; - bool is_dir; - QString rel_path; - QList children; - size_type size; - float progress; - int priority; - int index; // Index in torrent_info +private: + torrent_file *parent; + bool is_dir; + QString rel_path; + QList children; + size_type size; + float progress; + int priority; + int index; // Index in torrent_info - public: - torrent_file(torrent_file *parent, QString path, bool dir, size_type size=0, int index=-1, float progress=0., int priority=1): parent(parent), is_dir(dir), size(size), progress(progress), priority(priority), index(index){ - qDebug("created a file with index %d", index); - rel_path = QDir::cleanPath(path); - Q_ASSERT(progress >= 0.); - Q_ASSERT(progress <= 1.); - if(parent) { - parent->updateProgress(); - parent->updatePriority(priority); - } +public: + torrent_file(torrent_file *parent, QString path, bool dir, size_type size=0, int index=-1, float progress=0., int priority=1): parent(parent), is_dir(dir), size(size), progress(progress), priority(priority), index(index){ + qDebug("created a file with index %d", index); + rel_path = QDir::cleanPath(path); + Q_ASSERT(progress >= 0.); + Q_ASSERT(progress <= 1.); + if(parent) { + parent->updateProgress(); + parent->updatePriority(priority); } + } - ~torrent_file() { - qDeleteAll(children); - } + ~torrent_file() { + qDeleteAll(children); + } - QString path() const { - return rel_path; - } + QString path() const { + return rel_path; + } - QString name() const { - return rel_path.split(QDir::separator()).last(); - } + QString name() const { + return rel_path.split(QDir::separator()).last(); + } - void updateProgress() { - Q_ASSERT(is_dir); - if(children.isEmpty()) { - progress = 0.; - return; - } - double wanted = 0.; - double done = 0.; - foreach(const torrent_file *child, children) { - wanted += child->getSize(); - done += child->getSize()*child->getProgress(); - } - progress = done / wanted; - Q_ASSERT(progress >= 0.); - Q_ASSERT(progress <= 1.); + void updateProgress() { + Q_ASSERT(is_dir); + if(children.isEmpty()) { + progress = 0.; + return; } + double wanted = 0.; + double done = 0.; + foreach(const torrent_file *child, children) { + wanted += child->getSize(); + done += child->getSize()*child->getProgress(); + } + progress = done / wanted; + Q_ASSERT(progress >= 0.); + Q_ASSERT(progress <= 1.); + } - void updatePriority(int prio) { - Q_ASSERT(is_dir); - foreach(const torrent_file *child, children) { - if(child->getPriority() != prio) return; - } - priority = prio; + void updatePriority(int prio) { + Q_ASSERT(is_dir); + foreach(const torrent_file *child, children) { + if(child->getPriority() != prio) return; } + priority = prio; + } - int getPriority() const { - return priority; - } + int getPriority() const { + return priority; + } - size_type getSize() const { - return size; - } + size_type getSize() const { + return size; + } - float getProgress() const { - return progress; - } + float getProgress() const { + return progress; + } - int getIndex() const { - return index; - } + int getIndex() const { + return index; + } - bool isDir() const { - return is_dir; - } + bool isDir() const { + return is_dir; + } - bool hasChildren() const { - return (!children.isEmpty()); - } + bool hasChildren() const { + return (!children.isEmpty()); + } - QList getChildren() const { - return children; - } + QList getChildren() const { + return children; + } - const torrent_file* getChild(QString fileName) const { - Q_ASSERT(is_dir); - foreach(const torrent_file *f, children) { - if(f->name() == fileName) return f; - } - return 0; + const torrent_file* getChild(QString fileName) const { + Q_ASSERT(is_dir); + foreach(const torrent_file *f, children) { + if(f->name() == fileName) return f; } + return 0; + } - void addBytes(size_type b) { - size += b; - if(parent) - parent->addBytes(b); - } + void addBytes(size_type b) { + size += b; + if(parent) + parent->addBytes(b); + } - torrent_file* addChild(QString fileName, bool dir, size_type size=0, int index = -1, float progress=0., int priority=1) { - Q_ASSERT(is_dir); - qDebug("Adding a new child of size: %ld", (long)size); - torrent_file *f = new torrent_file(this, QDir::cleanPath(rel_path+QDir::separator()+fileName), dir, size, index, progress, priority); - children << f; - if(size) { - addBytes(size); - } - return f; + torrent_file* addChild(QString fileName, bool dir, size_type size=0, int index = -1, float progress=0., int priority=1) { + Q_ASSERT(is_dir); + qDebug("Adding a new child of size: %ld", (long)size); + torrent_file *f = new torrent_file(this, QDir::cleanPath(rel_path+QDir::separator()+fileName), dir, size, index, progress, priority); + children << f; + if(size) { + addBytes(size); } + return f; + } - bool removeFromFS(QString saveDir) const { - QString full_path = saveDir + QDir::separator() + rel_path; - if(!QFile::exists(full_path)) { - qDebug("%s does not exist, no need to remove it", full_path.toLocal8Bit().data()); - return true; - } - bool success = true; - qDebug("We have %d children", children.size()); - foreach(const torrent_file *f, children) { - bool s = f->removeFromFS(saveDir); - success = s && success; - } - if(is_dir) { - qDebug("trying to remove directory: %s", full_path.toLocal8Bit().data()); - QDir dir(full_path); - dir.rmdir(full_path); - } else { - qDebug("trying to remove file: %s", full_path.toLocal8Bit().data()); - bool s = QFile::remove(full_path); - success = s && success; - } - return success; + bool removeFromFS(QString saveDir) const { + QString full_path = saveDir + QDir::separator() + rel_path; + if(!QFile::exists(full_path)) { + qDebug("%s does not exist, no need to remove it", full_path.toLocal8Bit().data()); + return true; } + bool success = true; + qDebug("We have %d children", children.size()); + foreach(const torrent_file *f, children) { + bool s = f->removeFromFS(saveDir); + success = s && success; + } + if(is_dir) { + qDebug("trying to remove directory: %s", full_path.toLocal8Bit().data()); + QDir dir(full_path); + dir.rmdir(full_path); + } else { + qDebug("trying to remove file: %s", full_path.toLocal8Bit().data()); + bool s = QFile::remove(full_path); + success = s && success; + } + return success; + } }; class arborescence { - private: - torrent_file *root; +private: + torrent_file *root; - public: - arborescence(boost::intrusive_ptr t) { - torrent_info::file_iterator fi = t->begin_files(); - if(t->num_files() > 1) { - root = new torrent_file(0, misc::toQString(t->name()), true); - } else { - // XXX: Will crash if there is no file in torrent - root = new torrent_file(0, misc::toQString(t->name()), false, fi->size, 0); - return; - } - int i = 0; - while(fi != t->end_files()) { - QString path = QDir::cleanPath(misc::toQString(fi->path.string())); - addFile(path, fi->size, i); - fi++; - ++i; - } - qDebug("real size: %ld, tree size: %ld", (long)t->total_size(), (long)root->getSize()); - Q_ASSERT(root->getSize() == t->total_size()); +public: + arborescence(boost::intrusive_ptr t) { + torrent_info::file_iterator fi = t->begin_files(); + if(t->num_files() > 1) { + root = new torrent_file(0, misc::toQString(t->name()), true); + } else { + // XXX: Will crash if there is no file in torrent + root = new torrent_file(0, misc::toQString(t->name()), false, fi->size, 0); + return; } - - arborescence(torrent_info const& t, std::vector fp, std::vector files_priority) { - torrent_info::file_iterator fi = t.begin_files(); - if(t.num_files() > 1) { - qDebug("More than one file in the torrent, setting a folder as root"); - root = new torrent_file(0, misc::toQString(t.name()), true); - } else { - // XXX: Will crash if there is no file in torrent - qDebug("one file in the torrent, setting it as root with index 0"); - root = new torrent_file(0, misc::toQString(t.name()), false, fi->size, 0, ((double)fp[0])/t.file_at(0).size, files_priority.at(0)); - return; - } - int i = 0; - while(fi != t.end_files()) { - QString path = QDir::cleanPath(misc::toQString(fi->path.string())); - addFile(path, fi->size, i, ((double)fp[i])/t.file_at(i).size, files_priority.at(i)); - fi++; - ++i; - } - qDebug("real size: %ld, tree size: %ld", (long)t.total_size(), (long)root->getSize()); - Q_ASSERT(root->getSize() == t.total_size()); + int i = 0; + while(fi != t->end_files()) { + QString path = QDir::cleanPath(misc::toQString(fi->path.string())); + addFile(path, fi->size, i); + fi++; + ++i; } + qDebug("real size: %ld, tree size: %ld", (long)t->total_size(), (long)root->getSize()); + Q_ASSERT(root->getSize() == t->total_size()); + } - ~arborescence() { - delete root; + arborescence(torrent_info const& t, std::vector fp, std::vector files_priority) { + torrent_info::file_iterator fi = t.begin_files(); + if(t.num_files() > 1) { + qDebug("More than one file in the torrent, setting a folder as root"); + root = new torrent_file(0, misc::toQString(t.name()), true); + } else { + // XXX: Will crash if there is no file in torrent + qDebug("one file in the torrent, setting it as root with index 0"); + root = new torrent_file(0, misc::toQString(t.name()), false, fi->size, 0, ((double)fp[0])/t.file_at(0).size, files_priority.at(0)); + return; } - - torrent_file* getRoot() const { - return root; + int i = 0; + while(fi != t.end_files()) { + QString path = QDir::cleanPath(misc::toQString(fi->path.string())); + addFile(path, fi->size, i, ((double)fp[i])/t.file_at(i).size, files_priority.at(i)); + fi++; + ++i; } + qDebug("real size: %ld, tree size: %ld", (long)t.total_size(), (long)root->getSize()); + Q_ASSERT(root->getSize() == t.total_size()); + } - bool removeFromFS(QString saveDir) { - if(!QFile::exists(saveDir+QDir::separator()+root->path())) return true; - bool success = root->removeFromFS(saveDir); - QDir root_dir(root->path()); - root_dir.rmdir(saveDir+QDir::separator()+root->path()); - return success; - } + ~arborescence() { + delete root; + } - protected: - void addFile(QString path, size_type file_size, int index, float progress=0., int priority=1) { - Q_ASSERT(root->isDir()); - path = QDir::cleanPath(path); - //Q_ASSERT(path.startsWith(root->path())); - QString relative_path = path.remove(0, root->path().size()); - if(relative_path.at(0) ==QDir::separator()) - relative_path.remove(0, 1); - QStringList fileNames = relative_path.split(QDir::separator()); - torrent_file *dad = root; - unsigned int nb_i = 0; - unsigned int size = fileNames.size(); - foreach(const QString &fileName, fileNames) { - ++nb_i; - if(fileName == ".") continue; - const torrent_file* child = dad->getChild(fileName); - if(!child) { - if(nb_i != size) { - // Folder - child = dad->addChild(fileName, true); - } else { - // File - child = dad->addChild(fileName, false, file_size, index, progress, priority); - } + torrent_file* getRoot() const { + return root; + } + + bool removeFromFS(QString saveDir) { + if(!QFile::exists(saveDir+QDir::separator()+root->path())) return true; + bool success = root->removeFromFS(saveDir); + QDir root_dir(root->path()); + root_dir.rmdir(saveDir+QDir::separator()+root->path()); + return success; + } + +protected: + void addFile(QString path, size_type file_size, int index, float progress=0., int priority=1) { + Q_ASSERT(root->isDir()); + path = QDir::cleanPath(path); + //Q_ASSERT(path.startsWith(root->path())); + QString relative_path = path.remove(0, root->path().size()); + if(relative_path.at(0) ==QDir::separator()) + relative_path.remove(0, 1); + QStringList fileNames = relative_path.split(QDir::separator()); + torrent_file *dad = root; + unsigned int nb_i = 0; + unsigned int size = fileNames.size(); + foreach(const QString &fileName, fileNames) { + ++nb_i; + if(fileName == ".") continue; + const torrent_file* child = dad->getChild(fileName); + if(!child) { + if(nb_i != size) { + // Folder + child = dad->addChild(fileName, true); + } else { + // File + child = dad->addChild(fileName, false, file_size, index, progress, priority); } - dad = (torrent_file*)child; } + dad = (torrent_file*)child; } + } }; #endif diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index cc8fb0ca2..3dc2accc9 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -360,6 +360,123 @@ void bittorrent::loadWebSeeds(QString hash) { } } +QTorrentHandle bittorrent::addMagnetUri(QString magnet_uri, bool resumed) { + QTorrentHandle h; + QString hash = misc::magnetUriToHash(magnet_uri); + if(hash.isEmpty()) { + addConsoleMessage(tr("'%1' is not a valid magnet URI.").arg(magnet_uri)); + return h; + } + if(resumed) { + qDebug("Resuming magnet URI: %s", hash.toUtf8().data()); + } else { + qDebug("Adding new magnet URI"); + } + + bool fastResume=false; + Q_ASSERT(magnet_uri.startsWith("magnet:")); + QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); + // Checking if BT_backup Dir exists + // create it if it is not + if(! torrentBackup.exists()) { + if(! torrentBackup.mkpath(torrentBackup.path())) { + std::cerr << "Couldn't create the directory: '" << torrentBackup.path().toLocal8Bit().data() << "'\n"; + exit(1); + } + } + + // Check if torrent is already in download list + if(s->find_torrent(sha1_hash(hash.toUtf8().data())).is_valid()) { + qDebug("/!\\ Torrent is already in download list"); + // Update info Bar + addConsoleMessage(tr("'%1' is already in download list.", "e.g: 'xxx.avi' is already in download list.").arg(magnet_uri)); + return h; + } + + add_torrent_params p; + //Getting fast resume data if existing + std::vector buf; + if(resumed) { + qDebug("Trying to load fastresume data: %s", (torrentBackup.path()+QDir::separator()+hash+QString(".fastresume")).toLocal8Bit().data()); + if (load_file((torrentBackup.path()+QDir::separator()+hash+QString(".fastresume")).toLocal8Bit().data(), buf) == 0) { + fastResume = true; + p.resume_data = &buf; + qDebug("Successfuly loaded"); + } + } + QString savePath = getSavePath(hash); + qDebug("addMagnetURI: using save_path: %s", savePath.toUtf8().data()); + if(defaultTempPath.isEmpty() || (resumed && TorrentPersistentData::isSeed(hash))) { + p.save_path = savePath.toLocal8Bit().data(); + } else { + p.save_path = defaultTempPath.toLocal8Bit().data(); + } + // Preallocate all? + if(preAllocateAll) + p.storage_mode = storage_mode_allocate; + else + p.storage_mode = storage_mode_sparse; + // Start in pause + //p.paused = true; + p.duplicate_is_error = false; // Already checked + p.auto_managed = false; // Because it is added in paused state + // Adding torrent to bittorrent session + try { + h = QTorrentHandle(add_magnet_uri(*s, magnet_uri.toStdString(), p)); + }catch(std::exception e){ + qDebug("Error: %s", e.what()); + } + // Check if it worked + if(!h.is_valid()) { + // No need to keep on, it failed. + qDebug("/!\\ Error: Invalid handle"); + return h; + } + Q_ASSERT(h.hash() == hash); + // Connections limit per torrent + h.set_max_connections(maxConnecsPerTorrent); + // Uploads limit per torrent + h.set_max_uploads(maxUploadsPerTorrent); + // Load filtered files + if(resumed) { + // Load custom url seeds + loadWebSeeds(hash); + // Load speed limit from hard drive + loadTorrentSpeedLimits(hash); + // Load trackers + loadTrackerFile(hash); + // XXX: only when resuming because torrentAddition dialog is not supported yet + loadFilesPriorities(h); + } else { + // Sequential download + if(TorrentTempData::hasTempData(hash)) { + qDebug("addMagnetUri: Setting download as sequential (from tmp data)"); + h.set_sequential_download(TorrentTempData::isSequential(hash)); + } + // Save persistent data for new torrent + Q_ASSERT(h.is_valid()); + qDebug("addMagnetUri: hash: %s", h.hash().toUtf8().data()); + TorrentPersistentData::saveTorrentPersistentData(h, true); + qDebug("Persistent data saved"); + // Save save_path + if(!defaultTempPath.isEmpty()) { + qDebug("addMagnetUri: Saving save_path in persistent data: %s", savePath.toUtf8().data()); + TorrentPersistentData::saveSavePath(hash, savePath); + } + } + if(!addInPause && !fastResume) { + // Start torrent because it was added in paused state + h.resume(); + } + // Send torrent addition signal + if(fastResume) + addConsoleMessage(tr("'%1' resumed. (fast resume)", "'/home/y/xxx.torrent' was resumed. (fast resume)").arg(magnet_uri)); + else + addConsoleMessage(tr("'%1' added to download list.", "'/home/y/xxx.torrent' was added to download list.").arg(magnet_uri)); + emit addedTorrent(h); + return h; +} + // Add a torrent to the bittorrent session QTorrentHandle bittorrent::addTorrent(QString path, bool fromScanDir, QString from_url, bool resumed) { QTorrentHandle h; @@ -406,13 +523,7 @@ QTorrentHandle bittorrent::addTorrent(QString path, bool fromScanDir, QString fr qDebug(" -> Hash: %s", misc::toString(t->info_hash()).c_str()); qDebug(" -> Name: %s", t->name().c_str()); hash = misc::toQString(t->info_hash()); - if(file.startsWith(torrentBackup.path())) { - QFileInfo fi(file); - QString old_hash = fi.baseName(); - if(old_hash != hash){ - qDebug("* ERROR: Strange, hash changed from %s to %s", old_hash.toLocal8Bit().data(), hash.toLocal8Bit().data()); - } - } + // Check if torrent is already in download list if(s->find_torrent(t->info_hash()).is_valid()) { qDebug("/!\\ Torrent is already in download list"); @@ -726,7 +837,7 @@ void bittorrent::loadTorrentSpeedLimits(QString hash) { // Read pieces priorities from hard disk // and ask QTorrentHandle to consider them void bittorrent::loadFilesPriorities(QTorrentHandle &h) { - qDebug("Applying pieces priorities"); + qDebug("Applying files priority"); if(!h.is_valid()) { qDebug("/!\\ Error: Invalid handle"); return; @@ -1142,6 +1253,10 @@ void bittorrent::readAlerts() { bencode(std::ostream_iterator(out), *p->resume_data); } } + else if (metadata_received_alert* p = dynamic_cast(a.get())) { + QTorrentHandle h(p->handle); + emit metadataReceived(h); + } else if (file_error_alert* p = dynamic_cast(a.get())) { QTorrentHandle h(p->handle); h.auto_managed(false); @@ -1300,13 +1415,6 @@ void bittorrent::processDownloadedFile(QString url, QString file_path) { } } -void bittorrent::downloadFromURLList(const QStringList& url_list) { - qDebug("DownloadFromUrlList"); - foreach(const QString url, url_list) { - downloadFromUrl(url); - } -} - // Return current download rate for the BT // session. Payload means that it only take into // account "useful" part of the rate @@ -1352,7 +1460,7 @@ void bittorrent::startUpTorrents() { QStringList fileNames; QStringList known_torrents = TorrentPersistentData::knownTorrents(); if(isQueueingEnabled()) { - QList > filePaths; + QList > hashes; foreach(const QString &hash, known_torrents) { QString filePath; if(TorrentPersistentData::isMagnet(hash)) { @@ -1361,21 +1469,27 @@ void bittorrent::startUpTorrents() { filePath = torrentBackup.path()+QDir::separator()+hash+".torrent"; } int prio = TorrentPersistentData::getPriority(hash); - misc::insertSort2(filePaths, qMakePair(prio, filePath)); + misc::insertSort2(hashes, qMakePair(prio, hash)); } // Resume downloads - QPair fileName; - foreach(fileName, filePaths) { - addTorrent(fileName.second, false, QString(), true); + QPair couple; + foreach(couple, hashes) { + QString hash = couple.second; + qDebug("Starting up torrent %s", hash.toUtf8().data()); + if(TorrentPersistentData::isMagnet(hash)) { + addMagnetUri(TorrentPersistentData::getMagnetUri(hash), true); + } else { + addTorrent(torrentBackup.path()+QDir::separator()+hash+".torrent", false, QString(), true); + } } } else { - QStringList filePaths; - foreach(const QString &fileName, fileNames) { - filePaths.append(torrentBackup.path()+QDir::separator()+fileName); - } // Resume downloads - foreach(const QString &fileName, filePaths) { - addTorrent(fileName, false, QString(), true); + foreach(const QString &hash, known_torrents) { + qDebug("Starting up torrent %s", hash.toUtf8().data()); + if(TorrentPersistentData::isMagnet(hash)) + addMagnetUri(TorrentPersistentData::getMagnetUri(hash), true); + else + addTorrent(torrentBackup.path()+QDir::separator()+hash+".torrent", false, QString(), true); } } qDebug("Unfinished torrents resumed"); diff --git a/src/bittorrent.h b/src/bittorrent.h index e864ebab6..773045a7f 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -111,10 +111,10 @@ class bittorrent : public QObject { public slots: QTorrentHandle addTorrent(QString path, bool fromScanDir = false, QString from_url = QString(), bool resumed = false); + QTorrentHandle addMagnetUri(QString magnet_uri, bool resumed=false); void loadSessionState(); void saveSessionState(); void downloadFromUrl(QString url); - void downloadFromURLList(const QStringList& url_list); void deleteTorrent(QString hash, bool permanent = false); void startUpTorrents(); /* Needed by Web UI */ @@ -185,6 +185,7 @@ class bittorrent : public QObject { void updateFileSize(QString hash); void downloadFromUrlFailure(QString url, QString reason); void torrentFinishedChecking(QTorrentHandle& h); + void metadataReceived(QTorrentHandle &h); }; #endif diff --git a/src/downloadingTorrents.cpp b/src/downloadingTorrents.cpp index 16829090f..9d166edbb 100644 --- a/src/downloadingTorrents.cpp +++ b/src/downloadingTorrents.cpp @@ -53,8 +53,8 @@ DownloadingTorrents::DownloadingTorrents(QObject *parent, bittorrent *BTSession) actionSet_download_limit->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/downloading.png"))); actionDelete_Permanently->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete_perm.png"))); actionTorrent_Properties->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/properties.png"))); -// tabBottom->setTabIcon(0, QIcon(QString::fromUtf8(":/Icons/oxygen/log.png"))); -// tabBottom->setTabIcon(1, QIcon(QString::fromUtf8(":/Icons/oxygen/filter.png"))); + // tabBottom->setTabIcon(0, QIcon(QString::fromUtf8(":/Icons/oxygen/log.png"))); + // tabBottom->setTabIcon(1, QIcon(QString::fromUtf8(":/Icons/oxygen/filter.png"))); // Set Download list model DLListModel = new QStandardItemModel(0,10); @@ -79,6 +79,7 @@ DownloadingTorrents::DownloadingTorrents(QObject *parent, bittorrent *BTSession) loadHiddenColumns(); connect(BTSession, SIGNAL(torrentFinishedChecking(QTorrentHandle&)), this, SLOT(sortProgressColumn(QTorrentHandle&))); + connect(BTSession, SIGNAL(metadataReceived(QTorrentHandle&)), this, SLOT(updateMetadata(QTorrentHandle&))); // Load last columns width for download list if(!loadColWidthDLList()) { @@ -173,10 +174,12 @@ void DownloadingTorrents::showProperties(const QModelIndex &index) { void DownloadingTorrents::showPropertiesFromHash(QString hash) { QTorrentHandle h = BTSession->getTorrentHandle(hash); - properties *prop = new properties(this, BTSession, h); - connect(prop, SIGNAL(filteredFilesChanged(QString)), this, SLOT(updateFileSizeAndProgress(QString))); - connect(prop, SIGNAL(trackersChanged(QString)), BTSession, SLOT(saveTrackerFile(QString))); - prop->show(); + if(h.is_valid() && h.has_metadata()) { + properties *prop = new properties(this, BTSession, h); + connect(prop, SIGNAL(filteredFilesChanged(QString)), this, SLOT(updateFileSizeAndProgress(QString))); + connect(prop, SIGNAL(trackersChanged(QString)), BTSession, SLOT(saveTrackerFile(QString))); + prop->show(); + } } // Remove a torrent from the download list but NOT from the BT Session @@ -228,14 +231,14 @@ void DownloadingTorrents::propertiesSelection(){ } void DownloadingTorrents::forceRecheck() { - QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); - foreach(const QModelIndex &index, selectedIndexes){ - if(index.column() == NAME){ - QString hash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); - QTorrentHandle h = BTSession->getTorrentHandle(hash); - h.force_recheck(); - } + QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); + foreach(const QModelIndex &index, selectedIndexes){ + if(index.column() == NAME){ + QString hash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); + QTorrentHandle h = BTSession->getTorrentHandle(hash); + h.force_recheck(); } + } } void DownloadingTorrents::displayDLListMenu(const QPoint&) { @@ -243,13 +246,19 @@ void DownloadingTorrents::displayDLListMenu(const QPoint&) { // Enable/disable pause/start action given the DL state QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); bool has_pause = false, has_start = false, has_preview = false; + bool show_properties_entry = false; + QTorrentHandle h; + qDebug("Displaying menu"); foreach(const QModelIndex &index, selectedIndexes) { if(index.column() == NAME) { // Get the file name QString hash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); // Get handle and pause the torrent - QTorrentHandle h = BTSession->getTorrentHandle(hash); + h = BTSession->getTorrentHandle(hash); if(!h.is_valid()) continue; + if(h.has_metadata()) { + show_properties_entry = true; + } if(h.is_paused()) { if(!has_start) { myDLLlistMenu.addAction(actionStart); @@ -261,9 +270,9 @@ void DownloadingTorrents::displayDLListMenu(const QPoint&) { has_pause = true; } } - if(BTSession->isFilePreviewPossible(hash) && !has_preview) { - myDLLlistMenu.addAction(actionPreview_file); - has_preview = true; + if(h.has_metadata() && BTSession->isFilePreviewPossible(hash) && !has_preview) { + myDLLlistMenu.addAction(actionPreview_file); + has_preview = true; } if(has_pause && has_start && has_preview) break; } @@ -278,7 +287,8 @@ void DownloadingTorrents::displayDLListMenu(const QPoint&) { myDLLlistMenu.addAction(actionForce_recheck); myDLLlistMenu.addSeparator(); myDLLlistMenu.addAction(actionOpen_destination_folder); - myDLLlistMenu.addAction(actionTorrent_Properties); + if(show_properties_entry) + myDLLlistMenu.addAction(actionTorrent_Properties); if(BTSession->isQueueingEnabled()) { myDLLlistMenu.addSeparator(); myDLLlistMenu.addAction(actionIncreasePriority); @@ -378,7 +388,7 @@ bool DownloadingTorrents::loadHiddenColumns() { if(ishidden_list.size() == DLListModel->columnCount()-1) { unsigned int listSize = ishidden_list.size(); for(unsigned int i=0; iheader()->resizeSection(i, ishidden_list.at(i).toInt()); + downloadList->header()->resizeSection(i, ishidden_list.at(i).toInt()); } loaded = true; } @@ -433,36 +443,36 @@ void DownloadingTorrents::hideOrShowColumnPriority() { // getter, return the action hide or show whose id is index QAction* DownloadingTorrents::getActionHoSCol(int index) { switch(index) { - case NAME : - return actionHOSColName; + case NAME : + return actionHOSColName; break; - case SIZE : - return actionHOSColSize; + case SIZE : + return actionHOSColSize; break; - case PROGRESS : - return actionHOSColProgress; + case PROGRESS : + return actionHOSColProgress; break; - case DLSPEED : - return actionHOSColDownSpeed; + case DLSPEED : + return actionHOSColDownSpeed; break; - case UPSPEED : - return actionHOSColUpSpeed; + case UPSPEED : + return actionHOSColUpSpeed; break; - case SEEDSLEECH : - return actionHOSColSeedersLeechers; + case SEEDSLEECH : + return actionHOSColSeedersLeechers; break; - case RATIO : - return actionHOSColRatio; + case RATIO : + return actionHOSColRatio; break; - case ETA : - return actionHOSColEta; + case ETA : + return actionHOSColEta; break; - case PRIORITY : - return actionHOSColPriority; - break; - default : + case PRIORITY : + return actionHOSColPriority; + break; + default : return NULL; - } +} } QStringList DownloadingTorrents::getSelectedTorrents(bool only_one) const{ @@ -479,96 +489,106 @@ QStringList DownloadingTorrents::getSelectedTorrents(bool only_one) const{ return res; } +void DownloadingTorrents::updateMetadata(QTorrentHandle &h) { + QString hash = h.hash(); + int row = getRowFromHash(hash); + if(row != -1) { + qDebug("Updating torrent metadata in download list"); + DLListModel->setData(DLListModel->index(row, NAME), QVariant(h.name())); + DLListModel->setData(DLListModel->index(row, SIZE), QVariant((qlonglong)h.actual_size())); + } +} + // get information from torrent handles and // update download list accordingly bool DownloadingTorrents::updateTorrent(QTorrentHandle h) { - bool added = false; - try{ - QString hash = h.hash(); - int row = getRowFromHash(hash); - if(row == -1) { - qDebug("Info: Could not find filename in download list, adding it..."); - addTorrent(hash); - row = getRowFromHash(hash); - added = true; - } - Q_ASSERT(row != -1); - // Update Priority - if(BTSession->isQueueingEnabled()) { - DLListModel->setData(DLListModel->index(row, PRIORITY), QVariant((int)BTSession->getDlTorrentPriority(hash))); - if(h.is_queued()) { - if(h.state() == torrent_status::checking_files || h.state() == torrent_status::queued_for_checking) { - DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/time.png"))), Qt::DecorationRole); - if(!downloadList->isColumnHidden(PROGRESS)) { - DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)h.progress())); - } - }else { - DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/queued.png"))), Qt::DecorationRole); - if(!downloadList->isColumnHidden(ETA)) { - DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); - } - } - // Reset speeds and seeds/leech - DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); - DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.)); - DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0")); - setRowColor(row, QString::fromUtf8("grey")); - return added; - } - } - if(!downloadList->isColumnHidden(PROGRESS)) - DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)h.progress())); - // No need to update a paused torrent - if(h.is_paused()) return added; - // Parse download state - // Setting download state - switch(h.state()) { - case torrent_status::checking_files: - case torrent_status::queued_for_checking: + bool added = false; + try{ + QString hash = h.hash(); + int row = getRowFromHash(hash); + if(row == -1) { + qDebug("Info: Could not find filename in download list, adding it..."); + addTorrent(hash); + row = getRowFromHash(hash); + added = true; + } + Q_ASSERT(row != -1); + // Update Priority + if(BTSession->isQueueingEnabled()) { + DLListModel->setData(DLListModel->index(row, PRIORITY), QVariant((int)BTSession->getDlTorrentPriority(hash))); + if(h.is_queued()) { + if(h.state() == torrent_status::checking_files || h.state() == torrent_status::queued_for_checking) { DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/time.png"))), Qt::DecorationRole); - setRowColor(row, QString::fromUtf8("grey")); - break; - case torrent_status::downloading: - case torrent_status::downloading_metadata: - if(h.download_payload_rate() > 0) { - DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/downloading.png"))), Qt::DecorationRole); - if(!downloadList->isColumnHidden(ETA)) { - DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)BTSession->getETA(hash))); - } - setRowColor(row, QString::fromUtf8("green")); - }else{ - DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/stalled.png"))), Qt::DecorationRole); - if(!downloadList->isColumnHidden(ETA)) { - DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); - } - setRowColor(row, QApplication::palette().color(QPalette::WindowText)); + if(!downloadList->isColumnHidden(PROGRESS)) { + DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)h.progress())); } - if(!downloadList->isColumnHidden(DLSPEED)) { - DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)h.download_payload_rate())); - } - if(!downloadList->isColumnHidden(UPSPEED)) { - DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)h.upload_payload_rate())); - } - break; - default: + }else { + DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/queued.png"))), Qt::DecorationRole); if(!downloadList->isColumnHidden(ETA)) { DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); } + } + // Reset speeds and seeds/leech + DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); + DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.)); + DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0")); + setRowColor(row, QString::fromUtf8("grey")); + return added; } - if(!downloadList->isColumnHidden(SEEDSLEECH)) { - QString tmp = misc::toQString(h.num_seeds(), true); - if(h.num_complete() >= 0) - tmp.append(QString("(")+misc::toQString(h.num_complete())+QString(")")); - tmp.append(QString("/")+misc::toQString(h.num_peers() - h.num_seeds(), true)); - if(h.num_incomplete() >= 0) - tmp.append(QString("(")+misc::toQString(h.num_incomplete())+QString(")")); - DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant(tmp)); + } + if(!downloadList->isColumnHidden(PROGRESS)) + DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)h.progress())); + // No need to update a paused torrent + if(h.is_paused()) return added; + // Parse download state + // Setting download state + switch(h.state()) { + case torrent_status::checking_files: + case torrent_status::queued_for_checking: + DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/time.png"))), Qt::DecorationRole); + setRowColor(row, QString::fromUtf8("grey")); + break; + case torrent_status::downloading: + case torrent_status::downloading_metadata: + if(h.download_payload_rate() > 0) { + DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/downloading.png"))), Qt::DecorationRole); + if(!downloadList->isColumnHidden(ETA)) { + DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)BTSession->getETA(hash))); + } + setRowColor(row, QString::fromUtf8("green")); + }else{ + DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(QString::fromUtf8(":/Icons/skin/stalled.png"))), Qt::DecorationRole); + if(!downloadList->isColumnHidden(ETA)) { + DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); + } + setRowColor(row, QApplication::palette().color(QPalette::WindowText)); } - if(!downloadList->isColumnHidden(RATIO)) { - DLListModel->setData(DLListModel->index(row, RATIO), QVariant(misc::toQString(BTSession->getRealRatio(hash)))); + if(!downloadList->isColumnHidden(DLSPEED)) { + DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)h.download_payload_rate())); } - }catch(invalid_handle e) {} - return added; + if(!downloadList->isColumnHidden(UPSPEED)) { + DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)h.upload_payload_rate())); + } + break; + default: + if(!downloadList->isColumnHidden(ETA)) { + DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); + } + } + if(!downloadList->isColumnHidden(SEEDSLEECH)) { + QString tmp = misc::toQString(h.num_seeds(), true); + if(h.num_complete() >= 0) + tmp.append(QString("(")+misc::toQString(h.num_complete())+QString(")")); + tmp.append(QString("/")+misc::toQString(h.num_peers() - h.num_seeds(), true)); + if(h.num_incomplete() >= 0) + tmp.append(QString("(")+misc::toQString(h.num_incomplete())+QString(")")); + DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant(tmp)); + } + if(!downloadList->isColumnHidden(RATIO)) { + DLListModel->setData(DLListModel->index(row, RATIO), QVariant(misc::toQString(BTSession->getRealRatio(hash)))); + } + }catch(invalid_handle e) {} + return added; } void DownloadingTorrents::addTorrent(QString hash) { @@ -657,17 +677,17 @@ void DownloadingTorrents::toggleDownloadListSortOrder(int index) { sortOrder = (Qt::SortOrder)!(bool)downloadList->header()->sortIndicatorOrder(); } switch(index) { - case SIZE: - case ETA: - case UPSPEED: - case DLSPEED: - case PROGRESS: - case PRIORITY: - case RATIO: - sortDownloadListFloat(index, sortOrder); - break; - default: - sortDownloadListString(index, sortOrder); + case SIZE: + case ETA: + case UPSPEED: + case DLSPEED: + case PROGRESS: + case PRIORITY: + case RATIO: + sortDownloadListFloat(index, sortOrder); + break; + default: + sortDownloadListString(index, sortOrder); } QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); QString sortOrderLetter; @@ -699,16 +719,16 @@ void DownloadingTorrents::sortDownloadList(int index, Qt::SortOrder sortOrder) { downloadList->header()->setSortIndicator(index, sortOrder); } switch(index) { - case SIZE: - case ETA: - case UPSPEED: - case DLSPEED: - case PRIORITY: - case PROGRESS: - sortDownloadListFloat(index, sortOrder); - break; - default: - sortDownloadListString(index, sortOrder); + case SIZE: + case ETA: + case UPSPEED: + case DLSPEED: + case PRIORITY: + case PROGRESS: + sortDownloadListFloat(index, sortOrder); + break; + default: + sortDownloadListString(index, sortOrder); } } @@ -761,7 +781,7 @@ bool DownloadingTorrents::loadColWidthDLList() { } unsigned int listSize = width_list.size(); for(unsigned int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); + downloadList->header()->resizeSection(i, width_list.at(i).toInt()); } QVariantList visualIndexes = settings.value(QString::fromUtf8("DownloadListVisualIndexes"), QVariantList()).toList(); if(visualIndexes.size() != DLListModel->columnCount()-1) { diff --git a/src/downloadingTorrents.h b/src/downloadingTorrents.h index 30898bc76..870d04402 100644 --- a/src/downloadingTorrents.h +++ b/src/downloadingTorrents.h @@ -103,6 +103,7 @@ class DownloadingTorrents : public QWidget, public Ui::downloading{ void sortProgressColumn(QTorrentHandle& h); void loadLastSortedColumn(); void addTorrent(QString hash); + void updateMetadata(QTorrentHandle &h); }; diff --git a/src/misc.h b/src/misc.h index 55b591da2..b9a1cdc0b 100644 --- a/src/misc.h +++ b/src/misc.h @@ -47,231 +47,244 @@ using namespace libtorrent; /* Miscellaneaous functions that can be useful */ class misc : public QObject{ - Q_OBJECT + Q_OBJECT - public: - // Convert any type of variable to C++ String - // convert=true will convert -1 to 0 - template static std::string toString(const T& x, bool convert=false) { - std::ostringstream o; - if(!(o< static std::string toString(const T& x, bool convert=false) { + std::ostringstream o; + if(!(o< static QString toQString(const T& x, bool convert=false) { + std::ostringstream o; + if(!(o< static QByteArray toQByteArray(const T& x, bool convert=false) { + std::ostringstream o; + if(!(o< static T fromString(const std::string& s) { + T x; + std::istringstream i(s); + if(!(i>>x)) { + throw std::runtime_error("::fromString()"); + } + return x; + } + + // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB) + // use Binary prefix standards from IEC 60027-2 + // see http://en.wikipedia.org/wiki/Kilobyte + // value must be given in bytes + static QString friendlyUnit(float val) { + if(val < 0) + return tr("Unknown", "Unknown (size)"); + const QString units[5] = {tr("B", "bytes"), tr("KiB", "kibibytes (1024 bytes)"), tr("MiB", "mebibytes (1024 kibibytes)"), tr("GiB", "gibibytes (1024 mibibytes)"), tr("TiB", "tebibytes (1024 gibibytes)")}; + char i = 0; + while(val > 1024. && i++<6) + val /= 1024.; + return QString(QByteArray::number(val, 'f', 1)) + units[(int)i]; + } + + static bool isPreviewable(QString extension){ + extension = extension.toUpper(); + if(extension == "AVI") return true; + if(extension == "MP3") return true; + if(extension == "OGG") return true; + if(extension == "OGM") return true; + if(extension == "WMV") return true; + if(extension == "WMA") return true; + if(extension == "MPEG") return true; + if(extension == "MPG") return true; + if(extension == "ASF") return true; + if(extension == "QT") return true; + if(extension == "RM") return true; + if(extension == "RMVB") return true; + if(extension == "RMV") return true; + if(extension == "SWF") return true; + if(extension == "FLV") return true; + if(extension == "WAV") return true; + if(extension == "MOV") return true; + if(extension == "VOB") return true; + if(extension == "MID") return true; + if(extension == "AC3") return true; + if(extension == "MP4") return true; + if(extension == "MP2") return true; + if(extension == "AVI") return true; + if(extension == "FLAC") return true; + if(extension == "AU") return true; + if(extension == "MPE") return true; + if(extension == "MOV") return true; + if(extension == "MKV") return true; + if(extension == "AIF") return true; + if(extension == "AIFF") return true; + if(extension == "AIFC") return true; + if(extension == "RA") return true; + if(extension == "RAM") return true; + if(extension == "M4P") return true; + if(extension == "M4A") return true; + if(extension == "3GP") return true; + if(extension == "AAC") return true; + if(extension == "SWA") return true; + if(extension == "MPC") return true; + if(extension == "MPP") return true; + return false; + } + + // return qBittorrent config path + static QString qBittorrentPath() { + QString qBtPath = QDir::homePath()+QDir::separator()+QString::fromUtf8(".qbittorrent") + QDir::separator(); + // Create dir if it does not exist + if(!QFile::exists(qBtPath)){ + QDir dir(qBtPath); + dir.mkpath(qBtPath); + } + return qBtPath; + } + + static void fixTrackersTiers(std::vector trackers) { + unsigned int nbTrackers = trackers.size(); + for(unsigned int i=0; i static void insertSort(QList > &list, const QPair& value, Qt::SortOrder sortOrder) { + int i = 0; + if(sortOrder == Qt::AscendingOrder) { + while(i < list.size() and value.second > list.at(i).second) { + ++i; } - if(o.str() == "-1" && convert) - return "0"; - return o.str(); - } - - template static QString toQString(const T& x, bool convert=false) { - std::ostringstream o; - if(!(o< static QByteArray toQByteArray(const T& x, bool convert=false) { - std::ostringstream o; - if(!(o< static T fromString(const std::string& s) { - T x; - std::istringstream i(s); - if(!(i>>x)) { - throw std::runtime_error("::fromString()"); - } - return x; - } - - // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB) - // use Binary prefix standards from IEC 60027-2 - // see http://en.wikipedia.org/wiki/Kilobyte - // value must be given in bytes - static QString friendlyUnit(float val) { - if(val < 0) - return tr("Unknown", "Unknown (size)"); - const QString units[5] = {tr("B", "bytes"), tr("KiB", "kibibytes (1024 bytes)"), tr("MiB", "mebibytes (1024 kibibytes)"), tr("GiB", "gibibytes (1024 mibibytes)"), tr("TiB", "tebibytes (1024 gibibytes)")}; - char i = 0; - while(val > 1024. && i++<6) - val /= 1024.; - return QString(QByteArray::number(val, 'f', 1)) + units[(int)i]; - } - - static bool isPreviewable(QString extension){ - extension = extension.toUpper(); - if(extension == "AVI") return true; - if(extension == "MP3") return true; - if(extension == "OGG") return true; - if(extension == "OGM") return true; - if(extension == "WMV") return true; - if(extension == "WMA") return true; - if(extension == "MPEG") return true; - if(extension == "MPG") return true; - if(extension == "ASF") return true; - if(extension == "QT") return true; - if(extension == "RM") return true; - if(extension == "RMVB") return true; - if(extension == "RMV") return true; - if(extension == "SWF") return true; - if(extension == "FLV") return true; - if(extension == "WAV") return true; - if(extension == "MOV") return true; - if(extension == "VOB") return true; - if(extension == "MID") return true; - if(extension == "AC3") return true; - if(extension == "MP4") return true; - if(extension == "MP2") return true; - if(extension == "AVI") return true; - if(extension == "FLAC") return true; - if(extension == "AU") return true; - if(extension == "MPE") return true; - if(extension == "MOV") return true; - if(extension == "MKV") return true; - if(extension == "AIF") return true; - if(extension == "AIFF") return true; - if(extension == "AIFC") return true; - if(extension == "RA") return true; - if(extension == "RAM") return true; - if(extension == "M4P") return true; - if(extension == "M4A") return true; - if(extension == "3GP") return true; - if(extension == "AAC") return true; - if(extension == "SWA") return true; - if(extension == "MPC") return true; - if(extension == "MPP") return true; - return false; - } - - // return qBittorrent config path - static QString qBittorrentPath() { - QString qBtPath = QDir::homePath()+QDir::separator()+QString::fromUtf8(".qbittorrent") + QDir::separator(); - // Create dir if it does not exist - if(!QFile::exists(qBtPath)){ - QDir dir(qBtPath); - dir.mkpath(qBtPath); - } - return qBtPath; - } - - static void fixTrackersTiers(std::vector trackers) { - unsigned int nbTrackers = trackers.size(); - for(unsigned int i=0; i static void insertSort(QList > &list, const QPair& value, Qt::SortOrder sortOrder) { - int i = 0; - if(sortOrder == Qt::AscendingOrder) { - while(i < list.size() and value.second > list.at(i).second) { - ++i; - } - }else{ - while(i < list.size() and value.second < list.at(i).second) { - ++i; - } + template static void insertSort2(QList > &list, const QPair& value, Qt::SortOrder sortOrder=Qt::AscendingOrder) { + int i = 0; + if(sortOrder == Qt::AscendingOrder) { + while(i < list.size() and value.first > list.at(i).first) { + ++i; + } + }else{ + while(i < list.size() and value.first < list.at(i).first) { + ++i; } - list.insert(i, value); } + list.insert(i, value); + } - template static void insertSort2(QList > &list, const QPair& value, Qt::SortOrder sortOrder=Qt::AscendingOrder) { - int i = 0; - if(sortOrder == Qt::AscendingOrder) { - while(i < list.size() and value.first > list.at(i).first) { - ++i; - } - }else{ - while(i < list.size() and value.first < list.at(i).first) { - ++i; - } + // Can't use template class for QString because >,< use unicode code for sorting + // which is not what a human would expect when sorting strings. + static void insertSortString(QList > &list, QPair value, Qt::SortOrder sortOrder) { + int i = 0; + if(sortOrder == Qt::AscendingOrder) { + while(i < list.size() and QString::localeAwareCompare(value.second, list.at(i).second) > 0) { + ++i; + } + }else{ + while(i < list.size() and QString::localeAwareCompare(value.second, list.at(i).second) < 0) { + ++i; } - list.insert(i, value); } + list.insert(i, value); + } - // Can't use template class for QString because >,< use unicode code for sorting - // which is not what a human would expect when sorting strings. - static void insertSortString(QList > &list, QPair value, Qt::SortOrder sortOrder) { - int i = 0; - if(sortOrder == Qt::AscendingOrder) { - while(i < list.size() and QString::localeAwareCompare(value.second, list.at(i).second) > 0) { - ++i; - } - }else{ - while(i < list.size() and QString::localeAwareCompare(value.second, list.at(i).second) < 0) { - ++i; - } - } - list.insert(i, value); + static float getPluginVersion(QString filePath) { + QFile plugin(filePath); + if(!plugin.exists()){ + qDebug("%s plugin does not exist, returning 0.0", filePath.toLocal8Bit().data()); + return 0.0; } - - static float getPluginVersion(QString filePath) { - QFile plugin(filePath); - if(!plugin.exists()){ - qDebug("%s plugin does not exist, returning 0.0", filePath.toLocal8Bit().data()); - return 0.0; - } - if(!plugin.open(QIODevice::ReadOnly | QIODevice::Text)){ - return 0.0; - } - float version = 0.0; - while (!plugin.atEnd()){ - QByteArray line = plugin.readLine(); - if(line.startsWith("#VERSION: ")){ - line = line.split(' ').last(); - line.replace("\n", ""); - version = line.toFloat(); - qDebug("plugin %s version: %.2f", filePath.toLocal8Bit().data(), version); - break; - } - } - return version; + if(!plugin.open(QIODevice::ReadOnly | QIODevice::Text)){ + return 0.0; } + float version = 0.0; + while (!plugin.atEnd()){ + QByteArray line = plugin.readLine(); + if(line.startsWith("#VERSION: ")){ + line = line.split(' ').last(); + line.replace("\n", ""); + version = line.toFloat(); + qDebug("plugin %s version: %.2f", filePath.toLocal8Bit().data(), version); + break; + } + } + return version; + } - // Take a number of seconds and return an user-friendly - // time duration like "1d 2h 10m". - static QString userFriendlyDuration(qlonglong seconds) { - if(seconds < 0) { - return QString::fromUtf8("∞"); - } - if(seconds < 60) { - return tr("< 1m", "< 1 minute"); - } - int minutes = seconds / 60; - if(minutes < 60) { - return tr("%1m","e.g: 10minutes").arg(QString::QString::fromUtf8(misc::toString(minutes).c_str())); - } - int hours = minutes / 60; - minutes = minutes - hours*60; - if(hours < 24) { - return tr("%1h%2m", "e.g: 3hours 5minutes").arg(QString::fromUtf8(misc::toString(hours).c_str())).arg(QString::fromUtf8(misc::toString(minutes).c_str())); - } - int days = hours / 24; - hours = hours - days * 24; - if(days < 100) { - return tr("%1d%2h%3m", "e.g: 2days 10hours 2minutes").arg(QString::fromUtf8(misc::toString(days).c_str())).arg(QString::fromUtf8(misc::toString(hours).c_str())).arg(QString::fromUtf8(misc::toString(minutes).c_str())); - } + static QString magnetUriToHash(QString magnet_uri) { + QString hash = ""; + QRegExp reg("urn:btih:([A-Z2-7=]+)"); + int pos = reg.indexIn(magnet_uri); + if(pos > -1) { + sha1_hash sha1; + sha1.assign(base32decode(reg.cap(1).toStdString())); + hash = misc::toQString(sha1); + } + qDebug("magnetUriToHash: hash: %s", hash.toUtf8().data()); + return hash; + } + + // Take a number of seconds and return an user-friendly + // time duration like "1d 2h 10m". + static QString userFriendlyDuration(qlonglong seconds) { + if(seconds < 0) { return QString::fromUtf8("∞"); } + if(seconds < 60) { + return tr("< 1m", "< 1 minute"); + } + int minutes = seconds / 60; + if(minutes < 60) { + return tr("%1m","e.g: 10minutes").arg(QString::QString::fromUtf8(misc::toString(minutes).c_str())); + } + int hours = minutes / 60; + minutes = minutes - hours*60; + if(hours < 24) { + return tr("%1h%2m", "e.g: 3hours 5minutes").arg(QString::fromUtf8(misc::toString(hours).c_str())).arg(QString::fromUtf8(misc::toString(minutes).c_str())); + } + int days = hours / 24; + hours = hours - days * 24; + if(days < 100) { + return tr("%1d%2h%3m", "e.g: 2days 10hours 2minutes").arg(QString::fromUtf8(misc::toString(days).c_str())).arg(QString::fromUtf8(misc::toString(hours).c_str())).arg(QString::fromUtf8(misc::toString(minutes).c_str())); + } + return QString::fromUtf8("∞"); + } }; // Trick to get a portable sleep() function class SleeperThread : public QThread{ - public: - static void msleep(unsigned long msecs) - { - QThread::msleep(msecs); - } +public: + static void msleep(unsigned long msecs) + { + QThread::msleep(msecs); + } }; #endif diff --git a/src/properties_imp.cpp b/src/properties_imp.cpp index fb6e0ea6d..978f60db2 100644 --- a/src/properties_imp.cpp +++ b/src/properties_imp.cpp @@ -118,6 +118,7 @@ properties::properties(QWidget *parent, bittorrent *BTSession, QTorrentHandle &h h.file_progress(fp); std::vector files_priority = loadFilesPriorities(); // List files in torrent + h.get_torrent_info(); arborescence *arb = new arborescence(h.get_torrent_info(), fp, files_priority); addFilesToTree(arb->getRoot(), PropListModel->invisibleRootItem()); delete arb; @@ -137,7 +138,7 @@ properties::properties(QWidget *parent, bittorrent *BTSession, QTorrentHandle &h progressBarVbox->addWidget(progressBar); progressBarUpdater = new RealProgressBarThread(progressBar, h); progressBarUpdater->start(); -// progressBarUpdater->refresh(); + // progressBarUpdater->refresh(); connect(updateInfosTimer, SIGNAL(timeout()), progressBarUpdater, SLOT(refresh())); loadSettings(); } @@ -313,15 +314,20 @@ void properties::loadWebSeeds(){ std::vector properties::loadFilesPriorities(){ std::vector fp; QVariantList files_priority = TorrentPersistentData::getFilesPriority(hash); - foreach(const QVariant &var_prio, files_priority) { - int priority = var_prio.toInt(); - if( priority < 0 || priority > 7){ - // Normal priority as default - priority = 1; + if(files_priority.empty()) { + for(int i=0; i 7){ + // Normal priority as default + priority = 1; + } + fp.push_back(priority); } - fp.push_back(priority); } - return fp; } @@ -456,8 +462,8 @@ void properties::askWebSeed(){ bool ok; // Ask user for a new url seed QString url_seed = QInputDialog::getText(this, tr("New url seed", "New HTTP source"), - tr("New url seed:"), QLineEdit::Normal, - QString::fromUtf8("http://www."), &ok); + tr("New url seed:"), QLineEdit::Normal, + QString::fromUtf8("http://www."), &ok); if(!ok) return; qDebug("Adding %s web seed", url_seed.toLocal8Bit().data()); if(urlSeeds.indexOf(url_seed) != -1) { @@ -523,8 +529,8 @@ void properties::deleteSelectedTrackers(){ unsigned int nbTrackers = trackers.size(); if(nbTrackers == (unsigned int) selectedItems.size()){ QMessageBox::warning(this, tr("qBittorrent"), - tr("Trackers list can't be empty."), - QMessageBox::Ok); + tr("Trackers list can't be empty."), + QMessageBox::Ok); return; } foreach(QListWidgetItem *item, selectedItems){ @@ -660,30 +666,30 @@ void properties::on_incrementalDownload_stateChanged(int state){ } void properties::on_changeSavePathButton_clicked() { - QString dir; - QDir saveDir(h.save_path()); - if(saveDir.exists()){ - dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), h.save_path()); - }else{ - dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath()); - } - if(!dir.isNull()){ - // Check if savePath exists - QDir savePath(dir); - if(!savePath.exists()){ - if(!savePath.mkpath(savePath.path())){ - QMessageBox::critical(0, tr("Save path creation error"), tr("Could not create the save path")); - return; - } - } - // Save savepath - TorrentPersistentData::saveSavePath(hash, savePath.path()); - // Actually move storage - if(!BTSession->useTemporaryFolder() || h.is_seed()) - h.move_storage(savePath.path()); - // Update save_path in dialog - save_path->setText(savePath.path()); + QString dir; + QDir saveDir(h.save_path()); + if(saveDir.exists()){ + dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), h.save_path()); + }else{ + dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath()); + } + if(!dir.isNull()){ + // Check if savePath exists + QDir savePath(dir); + if(!savePath.exists()){ + if(!savePath.mkpath(savePath.path())){ + QMessageBox::critical(0, tr("Save path creation error"), tr("Could not create the save path")); + return; + } } + // Save savepath + TorrentPersistentData::saveSavePath(hash, savePath.path()); + // Actually move storage + if(!BTSession->useTemporaryFolder() || h.is_seed()) + h.move_storage(savePath.path()); + // Update save_path in dialog + save_path->setText(savePath.path()); + } } void properties::on_okButton_clicked(){ diff --git a/src/qtorrenthandle.cpp b/src/qtorrenthandle.cpp index f69c5610b..2221e2d71 100644 --- a/src/qtorrenthandle.cpp +++ b/src/qtorrenthandle.cpp @@ -35,6 +35,7 @@ #include #include "misc.h" #include "qtorrenthandle.h" +#include QTorrentHandle::QTorrentHandle(torrent_handle h): h(h) {} @@ -54,7 +55,7 @@ torrent_info QTorrentHandle::get_torrent_info() const { QString QTorrentHandle::hash() const { Q_ASSERT(h.is_valid()); - return misc::toQString(h.get_torrent_info().info_hash()); + return misc::toQString(h.info_hash()); } QString QTorrentHandle::name() const { @@ -74,13 +75,13 @@ float QTorrentHandle::progress() const { } bitfield QTorrentHandle::pieces() const { - Q_ASSERT(h.is_valid()); - return h.status().pieces; + Q_ASSERT(h.is_valid()); + return h.status().pieces; } void QTorrentHandle::get_download_queue(std::vector& queue) const { - Q_ASSERT(h.is_valid()); - h.get_download_queue(queue); + Q_ASSERT(h.is_valid()); + h.get_download_queue(queue); } QString QTorrentHandle::current_tracker() const { @@ -98,8 +99,8 @@ bool QTorrentHandle::is_paused() const { } bool QTorrentHandle::is_queued() const { - Q_ASSERT(h.is_valid()); - return h.is_paused() && h.is_auto_managed(); + Q_ASSERT(h.is_valid()); + return h.is_paused() && h.is_auto_managed(); } size_type QTorrentHandle::total_size() const { @@ -109,12 +110,12 @@ size_type QTorrentHandle::total_size() const { size_type QTorrentHandle::piece_length() const { Q_ASSERT(h.is_valid()); - return h.get_torrent_info().piece_length(); + return h.get_torrent_info().piece_length(); } int QTorrentHandle::num_pieces() const { Q_ASSERT(h.is_valid()); - return h.get_torrent_info().num_pieces(); + return h.get_torrent_info().num_pieces(); } size_type QTorrentHandle::total_wanted_done() const { @@ -170,12 +171,14 @@ fs::path QTorrentHandle::save_path_boost() const { QStringList QTorrentHandle::url_seeds() const { Q_ASSERT(h.is_valid()); QStringList res; - std::vector existing_seeds = h.get_torrent_info().url_seeds(); - unsigned int nbSeeds = existing_seeds.size(); - QString existing_seed; - for(unsigned int i=0; i existing_seeds = h.get_torrent_info().url_seeds(); + unsigned int nbSeeds = existing_seeds.size(); + QString existing_seed; + for(unsigned int i=0; i& fp) { } size_type QTorrentHandle::all_time_download() { - Q_ASSERT(h.is_valid()); - return h.status().all_time_download; + Q_ASSERT(h.is_valid()); + return h.status().all_time_download; } size_type QTorrentHandle::all_time_upload() { - Q_ASSERT(h.is_valid()); - return h.status().all_time_upload; + Q_ASSERT(h.is_valid()); + return h.status().all_time_upload; } size_type QTorrentHandle::total_payload_download() { @@ -301,8 +304,8 @@ QStringList QTorrentHandle::files_path() const { } int QTorrentHandle::queue_position() const { - Q_ASSERT(h.is_valid()); - return h.queue_position(); + Q_ASSERT(h.is_valid()); + return h.queue_position(); } int QTorrentHandle::num_uploads() const { @@ -321,13 +324,13 @@ bool QTorrentHandle::is_seed() const { } bool QTorrentHandle::is_auto_managed() const { - Q_ASSERT(h.is_valid()); - return h.is_auto_managed(); + Q_ASSERT(h.is_valid()); + return h.is_auto_managed(); } int QTorrentHandle::active_time() const { - Q_ASSERT(h.is_valid()); - return h.status().active_time; + Q_ASSERT(h.is_valid()); + return h.status().active_time; } bool QTorrentHandle::is_sequential_download() const { @@ -398,18 +401,18 @@ void QTorrentHandle::replace_trackers(std::vector const& v) cons } void QTorrentHandle::auto_managed(bool b) const { - Q_ASSERT(h.is_valid()); - h.auto_managed(b); + Q_ASSERT(h.is_valid()); + h.auto_managed(b); } void QTorrentHandle::queue_position_down() const { - Q_ASSERT(h.is_valid()); - h.queue_position_down(); + Q_ASSERT(h.is_valid()); + h.queue_position_down(); } void QTorrentHandle::queue_position_up() const { - Q_ASSERT(h.is_valid()); - h.queue_position_up(); + Q_ASSERT(h.is_valid()); + h.queue_position_up(); } @@ -429,18 +432,18 @@ void QTorrentHandle::set_tracker_login(QString username, QString password) { } void QTorrentHandle::force_recheck() const { - Q_ASSERT(h.is_valid()); - h.force_recheck(); + Q_ASSERT(h.is_valid()); + h.force_recheck(); } void QTorrentHandle::move_storage(QString new_path) const { - Q_ASSERT(h.is_valid()); - h.move_storage(new_path.toLocal8Bit().data()); + Q_ASSERT(h.is_valid()); + h.move_storage(new_path.toLocal8Bit().data()); } void QTorrentHandle::file_priority(int index, int priority) const { Q_ASSERT(h.is_valid()); - h.file_priority(index, priority); + h.file_priority(index, priority); } // @@ -453,6 +456,6 @@ QTorrentHandle& QTorrentHandle::operator =(const torrent_handle& new_h) { } bool QTorrentHandle::operator ==(const QTorrentHandle& new_h) const{ - QString hash = misc::toQString(h.get_torrent_info().info_hash()); + QString hash = misc::toQString(h.info_hash()); return (hash == new_h.hash()); } diff --git a/src/torrentPersistentData.h b/src/torrentPersistentData.h index b00540bf1..3d791e977 100644 --- a/src/torrentPersistentData.h +++ b/src/torrentPersistentData.h @@ -100,7 +100,7 @@ public: } static QString getSavePath(QString hash) { - QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); + QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); QHash all_data = settings.value("torrents-tmp", QHash()).toHash(); QHash data = all_data[hash].toHash(); if(data.contains("save_path")) @@ -144,6 +144,8 @@ public: } static void saveTorrentPersistentData(QTorrentHandle h, bool is_magnet = false) { + Q_ASSERT(h.is_valid()); + qDebug("Saving persistent data for %s", h.hash().toUtf8().data()); // First, remove temp data TorrentTempData::deleteTempData(h.hash()); Q_ASSERT(!TorrentTempData::hasTempData(h.hash())); @@ -151,7 +153,6 @@ public: QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); QHash all_data = settings.value("torrents", QHash()).toHash(); QHash data; - data["hash"] = h.hash(); data["is_magnet"] = is_magnet; if(is_magnet) { data["magnet_uri"] = misc::toQString(make_magnet_uri(h.get_torrent_handle())); @@ -175,15 +176,18 @@ public: tr_it++; } data["trackers"] = trackers; - QVariantList url_seeds; - foreach(QString url_seed, h.url_seeds()) { - url_seeds << url_seed; + if(!is_magnet) { + QVariantList url_seeds; + foreach(QString url_seed, h.url_seeds()) { + url_seeds << url_seed; + } + data["url_seeds"] = url_seeds; } - data["url_seeds"] = url_seeds; data["sequential"] = h.is_sequential_download(); // Save data all_data[h.hash()] = data; settings.setValue("torrents", all_data); + qDebug("TorrentPersistentData: Saving save_path %s, hash: %s", h.save_path().toUtf8().data(), h.hash().toUtf8().data()); } static void saveTrackers(QTorrentHandle h) { @@ -222,12 +226,14 @@ public: } static void saveSavePath(QString hash, QString save_path) { + Q_ASSERT(!hash.isEmpty()); QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); QHash all_data = settings.value("torrents", QHash()).toHash(); QHash data = all_data[hash].toHash(); data["save_path"] = save_path; all_data[hash] = data; settings.setValue("torrents", all_data); + qDebug("TorrentPersistentData: Saving save_path: %s, hash: %s", save_path.toUtf8().data(), hash.toUtf8().data()); } static void saveUrlSeeds(QTorrentHandle h) { @@ -289,6 +295,7 @@ public: QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); QHash all_data = settings.value("torrents", QHash()).toHash(); QHash data = all_data[hash].toHash(); + qDebug("TorrentPersistentData: getSavePath %s", data["save_path"].toString().toUtf8().data()); return data["save_path"].toString(); } @@ -303,7 +310,10 @@ public: QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent-resume")); QHash all_data = settings.value("torrents", QHash()).toHash(); QHash data = all_data[hash].toHash(); - return data["url_seeds"].toList(); + if(data.contains("url_seeds")) { + return data["url_seeds"].toList(); + } + return QVariantList(); } static bool isSeed(QString hash) {