Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add save path and category editing to WebUI #9228

Merged
merged 3 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/base/bittorrent/torrenthandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1213,12 +1213,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;
Expand Down
14 changes: 10 additions & 4 deletions src/webui/api/synccontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace "map" with "dictionary" since such term is used in other place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are used throughout the documentation

// - "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.
Expand Down Expand Up @@ -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;

Expand Down
27 changes: 24 additions & 3 deletions src/webui/api/torrentscontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
glassez marked this conversation as resolved.
Show resolved Hide resolved

// try to create the location if it does not exist
if (!QDir(newLocation).mkpath("."))
Expand Down Expand Up @@ -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))
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions src/webui/api/torrentscontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private slots:
void renameAction();
void setCategoryAction();
void createCategoryAction();
void editCategoryAction();
void removeCategoriesAction();
void addAction();
void deleteAction();
Expand Down
3 changes: 2 additions & 1 deletion src/webui/extra_translations.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = {
Expand Down
3 changes: 3 additions & 0 deletions src/webui/www/private/filters.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
CreateCategory: function(element, ref) {
createCategoryFN();
},
EditCategory: function(element, ref) {
editCategoryFN(element.id);
},
DeleteCategory: function(element, ref) {
removeCategoryFN(element.id);
},
Expand Down
1 change: 1 addition & 0 deletions src/webui/www/private/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ <h1 class="applicationTitle">qBittorrent Web User Interface <span class="version
</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>
Expand Down
136 changes: 98 additions & 38 deletions src/webui/www/private/newcategory.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,113 @@
<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();
}
}
});
newCategoryKeyboardEvents.activate();

window.addEvent('domready', function() {
$('newCategory').focus();
$('newCategoryButton').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]");
var uriAction = safeTrim(new URI().getData('action'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is possible to write it as: String(new URI().getData('action')).trim(); instead of adding a new function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String(null) returns "null" and String(undefined) returns "undefined" in JavaScript.

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;
}
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();

$('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();

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;
}
});
});
Expand All @@ -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>
Expand Down
22 changes: 15 additions & 7 deletions src/webui/www/private/scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,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']) {
Expand Down
8 changes: 6 additions & 2 deletions src/webui/www/private/scripts/contextmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
});
11 changes: 11 additions & 0 deletions src/webui/www/private/scripts/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,14 @@ function escapeHtml(str) {
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}

function safeTrim(value) {
Copy link
Member

@Chocobo1 Chocobo1 Aug 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String(null) returns "null" and String(undefined) returns "undefined" in JavaScript.

OK, however I think a true "safe trim" should look like this:

function safeTrim(str) {
    try {
        return str.trim();
    }
    catch (e) {
        if (e instanceof TypeError)
            return "";
        throw e;
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will update the implementation.

try {
return value.trim();
}
catch (e) {
if (e instanceof TypeError)
return "";
throw e;
}
}
Loading