diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 6efebf01d..10d790f01 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -1424,16 +1424,6 @@ bool TorrentHandle::saveTorrentFile(const QString &path) return false; } -void TorrentHandle::setFilePriority(int index, int priority) -{ - std::vector priorities = m_nativeHandle.file_priorities(); - - if ((priorities.size() > static_cast(index)) && (priorities[index] != priority)) { - priorities[index] = priority; - prioritizeFiles(QVector::fromStdVector(priorities)); - } -} - void TorrentHandle::handleStateUpdate(const libt::torrent_status &nativeStatus) { updateStatus(nativeStatus); diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 03540fab9..df66766e3 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -348,7 +348,6 @@ namespace BitTorrent void renameFile(int index, const QString &name); bool saveTorrentFile(const QString &path); void prioritizeFiles(const QVector &priorities); - void setFilePriority(int index, int priority); void setRatioLimit(qreal limit); void setSeedingTimeLimit(int limit); void setUploadLimit(int limit); diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 1082f829d..3d3ad5481 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -39,6 +39,7 @@ #include #include +#include "base/bittorrent/filepriority.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/torrentinfo.h" @@ -371,7 +372,7 @@ void TorrentsController::filesAction() const QString hash {params()["hash"]}; QVariantList fileList; - BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + const BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); if (!torrent) throw APIError(APIErrorType::NotFound); @@ -561,12 +562,38 @@ void TorrentsController::filePrioAction() checkParams({"hash", "id", "priority"}); const QString hash = params()["hash"]; - int fileID = params()["id"].toInt(); - int priority = params()["priority"].toInt(); + bool ok = false; + const int priority = params()["priority"].toInt(&ok); + if (!ok) + throw APIError(APIErrorType::BadParams, tr("Priority must be an integer")); + + if (!BitTorrent::isValidFilePriority(static_cast(priority))) + throw APIError(APIErrorType::BadParams, tr("Priority is not valid")); + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + if (!torrent) + throw APIError(APIErrorType::NotFound); + if (!torrent->hasMetadata()) + throw APIError(APIErrorType::Conflict, tr("Torrent's metadata has not yet downloaded")); + + const int filesCount = torrent->filesCount(); + QVector priorities = torrent->filePriorities(); + bool priorityChanged = false; + for (const QString &fileID : params()["id"].split('|')) { + const int id = fileID.toInt(&ok); + if (!ok) + throw APIError(APIErrorType::BadParams, tr("File IDs must be integers")); + if ((id < 0) || (id >= filesCount)) + throw APIError(APIErrorType::Conflict, tr("File ID is not valid")); + + if (priorities[id] != priority) { + priorities[id] = priority; + priorityChanged = true; + } + } - if (torrent && torrent->hasMetadata()) - torrent->setFilePriority(fileID, priority); + if (priorityChanged) + torrent->prioritizeFiles(priorities); } void TorrentsController::uploadLimitAction() diff --git a/src/webui/www/private/scripts/prop-files.js b/src/webui/www/private/scripts/prop-files.js index de107140a..c1511dcb2 100644 --- a/src/webui/www/private/scripts/prop-files.js +++ b/src/webui/www/private/scripts/prop-files.js @@ -47,28 +47,34 @@ var switchCBState = function() { if ($("all_files_cb").hasClass("partial")) { $("all_files_cb").removeClass("partial"); // Uncheck all checkboxes + var indexes = []; $$('input.DownloadedCB').each(function(item, index) { item.erase("checked"); - setFilePriority(index, 0); + indexes.push(index); }); + setFilePriority(indexes, 0); return; } if ($("all_files_cb").hasClass("checked")) { $("all_files_cb").removeClass("checked"); // Uncheck all checkboxes + var indexes = []; $$('input.DownloadedCB').each(function(item, index) { item.erase("checked"); - setFilePriority(index, 0); + indexes.push(index); }); + setFilePriority(indexes, 0); return; } // Check $("all_files_cb").addClass("checked"); // Check all checkboxes + var indexes = []; $$('input.DownloadedCB').each(function(item, index) { item.set("checked", "checked"); - setFilePriority(index, 1); + indexes.push(index); }); + setFilePriority(indexes, 1); }; var allCBChecked = function() { @@ -93,22 +99,31 @@ var allCBUnchecked = function() { var setFilePriority = function(id, priority) { if (current_hash === "") return; + var ids = Array.isArray(id) ? id : [id]; + new Request({ url: 'api/v2/torrents/filePrio', method: 'post', data: { 'hash': current_hash, - 'id': id, + 'id': ids.join('|'), 'priority': priority } }).send(); // Display or add combobox if (priority > 0) { - $('comboPrio' + id).set("value", 1); - $('comboPrio' + id).removeClass("invisible"); + ids.forEach(function(_id) { + if ($('comboPrio' + _id).hasClass("invisible")) { + $('comboPrio' + _id).set("value", priority); + $('comboPrio' + _id).removeClass("invisible"); + } + }); } else { - $('comboPrio' + id).addClass("invisible"); + ids.forEach(function(_id) { + if (!$('comboPrio' + _id).hasClass("invisible")) + $('comboPrio' + _id).addClass("invisible"); + }); } }; @@ -146,24 +161,27 @@ var createPriorityCombo = function(id, selected_prio) { var new_prio = $('comboPrio' + id).get('value'); setFilePriority(id, new_prio); }); - var opt = new Element("option"); - opt.set('value', '1'); - opt.set('html', "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]"); - if (selected_prio <= 1) - opt.setAttribute('selected', ''); - opt.injectInside(select); - opt = new Element("option"); - opt.set('value', '2'); - opt.set('html', "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]"); - if (selected_prio == 2) - opt.setAttribute('selected', ''); - opt.injectInside(select); - opt = new Element("option"); - opt.set('value', '7'); - opt.set('html', "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]"); - if (selected_prio == 7) - opt.setAttribute('selected', ''); - opt.injectInside(select); + + function createOptionElement(priority, html) { + var elem = new Element("option"); + elem.set('value', priority.toString()); + elem.set('html', html); + if (selected_prio == priority) + elem.setAttribute('selected', 'true'); + return elem; + } + + var normal = createOptionElement(1, "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]"); + if (selected_prio <= 0) + normal.setAttribute('selected', ''); + normal.injectInside(select); + + var high = createOptionElement(2, "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]"); + high.injectInside(select); + + var maximum = createOptionElement(7, "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]"); + maximum.injectInside(select); + if (is_seed || selected_prio < 1) { select.addClass("invisible"); } @@ -203,19 +221,20 @@ var filesDynTable = new Class({ var tds = tr.getElements('td'); for (var i = 0; i < row.length; ++i) { switch (i) { - case 0: + case 0: // checkbox if (row[i] > 0) tds[i].getChildren('input')[0].set('checked', 'checked'); else tds[i].getChildren('input')[0].removeProperty('checked'); break; - case 3: + case 3: // progress bar $('pbf_' + id).setValue(row[i].toFloat()); break; - case 4: + case 4: // download priority if (!is_seed && row[i] > 0) { tds[i].getChildren('select').set('value', row[i]); - $('comboPrio' + id).removeClass("invisible"); + if ($('comboPrio' + id).hasClass("invisible")) + $('comboPrio' + id).removeClass("invisible"); } else { if (!$('comboPrio' + id).hasClass("invisible"))