mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-12-21 08:09:35 +08:00
Merge pull request #9228 from Piccirello/addCategory
Add save path and category editing to WebUI
This commit is contained in:
commit
28a6ac3197
@ -1210,12 +1210,8 @@ void TorrentHandle::setName(const QString &name)
|
||||
bool TorrentHandle::setCategory(const QString &category)
|
||||
{
|
||||
if (m_category != category) {
|
||||
if (!category.isEmpty()) {
|
||||
if (!Session::isValidCategoryName(category)) return false;
|
||||
if (!m_session->categories().contains(category))
|
||||
if (!m_session->addCategory(category))
|
||||
return false;
|
||||
}
|
||||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||
return false;
|
||||
|
||||
QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
|
@ -320,7 +320,7 @@ namespace
|
||||
// - "full_update": full data update flag
|
||||
// - "torrents": dictionary contains information about torrents.
|
||||
// - "torrents_removed": a list of hashes of removed torrents
|
||||
// - "categories": list of categories
|
||||
// - "categories": map of categories info
|
||||
// - "categories_removed": list of removed categories
|
||||
// - "server_state": map contains information about the state of the server
|
||||
// The keys of the 'torrents' dictionary are hashes of torrents.
|
||||
@ -399,9 +399,15 @@ void SyncController::maindataAction()
|
||||
|
||||
data["torrents"] = torrents;
|
||||
|
||||
QVariantList categories;
|
||||
for (auto i = session->categories().cbegin(); i != session->categories().cend(); ++i)
|
||||
categories << i.key();
|
||||
QVariantHash categories;
|
||||
const auto categoriesList = session->categories();
|
||||
for (auto it = categoriesList.cbegin(); it != categoriesList.cend(); ++it) {
|
||||
const auto key = it.key();
|
||||
categories[key] = QVariantMap {
|
||||
{"name", key},
|
||||
{"savePath", it.value()}
|
||||
};
|
||||
}
|
||||
|
||||
data["categories"] = categories;
|
||||
|
||||
|
@ -738,7 +738,7 @@ void TorrentsController::setLocationAction()
|
||||
const QString newLocation {params()["location"].trimmed()};
|
||||
|
||||
if (newLocation.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Save path is empty"));
|
||||
throw APIError(APIErrorType::BadParams, tr("Save path cannot be empty"));
|
||||
|
||||
// try to create the location if it does not exist
|
||||
if (!QDir(newLocation).mkpath("."))
|
||||
@ -809,6 +809,7 @@ void TorrentsController::setCategoryAction()
|
||||
|
||||
const QStringList hashes {params()["hashes"].split('|')};
|
||||
const QString category {params()["category"].trimmed()};
|
||||
|
||||
applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
if (!torrent->setCategory(category))
|
||||
@ -821,10 +822,30 @@ void TorrentsController::createCategoryAction()
|
||||
checkParams({"category"});
|
||||
|
||||
const QString category {params()["category"].trimmed()};
|
||||
if (!BitTorrent::Session::isValidCategoryName(category) && !category.isEmpty())
|
||||
const QString savePath {params()["savePath"]};
|
||||
|
||||
if (category.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Category cannot be empty"));
|
||||
|
||||
if (!BitTorrent::Session::isValidCategoryName(category))
|
||||
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
|
||||
|
||||
BitTorrent::Session::instance()->addCategory(category);
|
||||
if (!BitTorrent::Session::instance()->addCategory(category, savePath))
|
||||
throw APIError(APIErrorType::Conflict, tr("Unable to create category"));
|
||||
}
|
||||
|
||||
void TorrentsController::editCategoryAction()
|
||||
{
|
||||
checkParams({"category", "savePath"});
|
||||
|
||||
const QString category {params()["category"].trimmed()};
|
||||
const QString savePath {params()["savePath"]};
|
||||
|
||||
if (category.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Category cannot be empty"));
|
||||
|
||||
if (!BitTorrent::Session::instance()->editCategory(category, savePath))
|
||||
throw APIError(APIErrorType::Conflict, tr("Unable to edit category"));
|
||||
}
|
||||
|
||||
void TorrentsController::removeCategoriesAction()
|
||||
|
@ -53,6 +53,7 @@ private slots:
|
||||
void renameAction();
|
||||
void setCategoryAction();
|
||||
void createCategoryAction();
|
||||
void editCategoryAction();
|
||||
void removeCategoriesAction();
|
||||
void addAction();
|
||||
void deleteAction();
|
||||
|
@ -78,7 +78,8 @@ const char *QBT_WEBUI_TRANSLATIONS[] = {
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Set location"),
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Limit upload rate"),
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Limit download rate"),
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Rename torrent")
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Rename torrent"),
|
||||
QT_TRANSLATE_NOOP("HttpServer", "Unable to create category")
|
||||
};
|
||||
|
||||
const struct { const char *source; const char *comment; } QBT_WEBUI_COMMENTED_TRANSLATIONS[] = {
|
||||
|
@ -30,6 +30,9 @@
|
||||
CreateCategory: function(element, ref) {
|
||||
createCategoryFN();
|
||||
},
|
||||
EditCategory: function(element, ref) {
|
||||
editCategoryFN(element.id);
|
||||
},
|
||||
DeleteCategory: function(element, ref) {
|
||||
removeCategoryFN(element.id);
|
||||
},
|
||||
|
@ -143,6 +143,7 @@
|
||||
</ul>
|
||||
<ul id="categoriesFilterMenu" class="contextMenu">
|
||||
<li><a href="#CreateCategory"><img src="images/qbt-theme/list-add.svg" alt="QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
|
||||
<li><a href="#EditCategory"><img src="images/qbt-theme/document-edit.svg" alt="QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
|
||||
<li><a href="#DeleteCategory"><img src="images/qbt-theme/list-remove.svg" alt="QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
|
||||
<li><a href="#DeleteUnusedCategories"><img src="images/qbt-theme/list-remove.svg" alt="QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
|
||||
<li class="separator"><a href="#StartTorrentsByCategory"><img src="images/qbt-theme/media-playback-start.svg" alt="QBT_TR(Resume torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Resume torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
|
||||
|
@ -7,12 +7,13 @@
|
||||
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
||||
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
|
||||
<script src="scripts/lib/mootools-1.2-more.js"></script>
|
||||
<script src="scripts/misc.js"></script>
|
||||
<script>
|
||||
var newCategoryKeyboardEvents = new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
events: {
|
||||
'enter': function(event) {
|
||||
$('newCategoryButton').click();
|
||||
$('categoryNameButton').click();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@ -20,42 +21,99 @@
|
||||
newCategoryKeyboardEvents.activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
$('newCategory').focus();
|
||||
$('newCategoryButton').addEvent('click', function(e) {
|
||||
var uriAction = safeTrim(new URI().getData('action'));
|
||||
var uriHashes = safeTrim(new URI().getData('hashes'));
|
||||
var uriCategoryName = safeTrim(new URI().getData('categoryName'));
|
||||
var uriSavePath = safeTrim(new URI().getData('savePath'));
|
||||
|
||||
if (uriAction === "create") {
|
||||
$('categoryName').focus();
|
||||
}
|
||||
else if (uriAction === "edit") {
|
||||
if (!uriCategoryName)
|
||||
return false;
|
||||
|
||||
$('categoryName').set('disabled', true);
|
||||
$('categoryName').set('value', escapeHtml(uriCategoryName));
|
||||
$('savePath').set('value', escapeHtml(uriSavePath));
|
||||
$('savePath').focus();
|
||||
}
|
||||
|
||||
$('categoryNameButton').addEvent('click', function(e) {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
var categoryName = $('newCategory').value.trim();
|
||||
if (categoryName == null || categoryName == "")
|
||||
return false;
|
||||
if (categoryName.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
|
||||
alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
|
||||
return false;
|
||||
}
|
||||
var hashesList = new URI().getData('hashes');
|
||||
if (!hashesList) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
category: categoryName
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
else {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: hashesList,
|
||||
category: categoryName
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
|
||||
var savePath = $('savePath').value.trim();
|
||||
var categoryName = $('categoryName').value.trim();
|
||||
|
||||
var verifyCategoryName = function(name) {
|
||||
if ((name === null) || (name === ""))
|
||||
return false;
|
||||
if (name.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
|
||||
alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (uriAction) {
|
||||
case "set":
|
||||
if ((uriHashes === "") || !verifyCategoryName(categoryName))
|
||||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
category: categoryName,
|
||||
savePath: savePath
|
||||
},
|
||||
onSuccess: function() {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
hashes: uriHashes,
|
||||
category: categoryName
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
},
|
||||
onError: function() {
|
||||
alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + escapeHtml(categoryName));
|
||||
}
|
||||
}).send();
|
||||
break;
|
||||
case "create":
|
||||
if (!verifyCategoryName(categoryName))
|
||||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
category: categoryName,
|
||||
savePath: savePath
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
break;
|
||||
case "edit":
|
||||
new Request({
|
||||
url: 'api/v2/torrents/editCategory',
|
||||
method: 'post',
|
||||
data: {
|
||||
category: uriCategoryName, // category name can't be changed
|
||||
savePath: savePath
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.closeWindows();
|
||||
}
|
||||
}).send();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -65,9 +123,11 @@
|
||||
<body>
|
||||
<div style="padding: 10px 10px 0px 10px;">
|
||||
<p style="font-weight: bold;">QBT_TR(Category)QBT_TR[CONTEXT=TransferListWidget]:</p>
|
||||
<input type="text" id="newCategory" value="" maxlength="100" style="width: 220px;" />
|
||||
<input type="text" id="categoryName" value="" maxlength="100" style="width: 220px;" />
|
||||
<p style="font-weight: bold;">QBT_TR(Save path)QBT_TR[CONTEXT=TransferListWidget]:</p>
|
||||
<input type="text" id="savePath" value="" style="width: 220px;" />
|
||||
<div style="text-align: center; padding-top: 10px;">
|
||||
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="newCategoryButton" />
|
||||
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="categoryNameButton" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -327,13 +327,21 @@ window.addEvent('load', function() {
|
||||
syncMainDataLastResponseId = response['rid'];
|
||||
}
|
||||
if (response['categories']) {
|
||||
response['categories'].each(function(category) {
|
||||
var categoryHash = genHash(category);
|
||||
category_list[categoryHash] = {
|
||||
name: category,
|
||||
torrents: []
|
||||
};
|
||||
});
|
||||
for (var key in response['categories']) {
|
||||
var category = response['categories'][key];
|
||||
var categoryHash = genHash(key);
|
||||
if (category_list[categoryHash] !== undefined) {
|
||||
// only the save path can change for existing categories
|
||||
category_list[categoryHash].savePath = category.savePath;
|
||||
}
|
||||
else {
|
||||
category_list[categoryHash] = {
|
||||
name: category.name,
|
||||
savePath: category.savePath,
|
||||
torrents: []
|
||||
};
|
||||
}
|
||||
}
|
||||
update_categories = true;
|
||||
}
|
||||
if (response['categories_removed']) {
|
||||
|
@ -387,9 +387,13 @@ var CategoriesFilterContextMenu = new Class({
|
||||
Extends: ContextMenu,
|
||||
updateMenuItems: function() {
|
||||
var id = this.options.element.id;
|
||||
if (id != CATEGORIES_ALL && id != CATEGORIES_UNCATEGORIZED)
|
||||
if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) {
|
||||
this.showItem('EditCategory');
|
||||
this.showItem('DeleteCategory');
|
||||
else
|
||||
}
|
||||
else {
|
||||
this.hideItem('EditCategory');
|
||||
this.hideItem('DeleteCategory');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -120,3 +120,14 @@ function escapeHtml(str) {
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function safeTrim(value) {
|
||||
try {
|
||||
return value.trim();
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof TypeError)
|
||||
return "";
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -458,20 +458,21 @@ initializeWindows = function() {
|
||||
};
|
||||
|
||||
torrentNewCategoryFN = function() {
|
||||
var action = "set";
|
||||
var hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'newCategoryPage',
|
||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newcategory.html?hashes=' + hashes.join('|'),
|
||||
contentURL: 'newcategory.html?action=' + action + '&hashes=' + hashes.join('|'),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
width: 250,
|
||||
height: 100
|
||||
height: 150
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -494,18 +495,39 @@ initializeWindows = function() {
|
||||
};
|
||||
|
||||
createCategoryFN = function() {
|
||||
var action = "create";
|
||||
new MochaUI.Window({
|
||||
id: 'newCategoryPage',
|
||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newcategory.html',
|
||||
contentURL: 'newcategory.html?action=' + action,
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
width: 250,
|
||||
height: 100
|
||||
height: 150
|
||||
});
|
||||
updateMainData();
|
||||
};
|
||||
|
||||
editCategoryFN = function(categoryHash) {
|
||||
var action = "edit";
|
||||
var categoryName = category_list[categoryHash].name;
|
||||
var savePath = category_list[categoryHash].savePath;
|
||||
new MochaUI.Window({
|
||||
id: 'editCategoryPage',
|
||||
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newcategory.html?action=' + action + '&categoryName=' + categoryName + '&savePath=' + savePath,
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
width: 250,
|
||||
height: 150
|
||||
});
|
||||
updateMainData();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user