diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md
index 375e24d69..4dd39c4a8 100644
--- a/WebAPI_Changelog.md
+++ b/WebAPI_Changelog.md
@@ -1,5 +1,10 @@
# WebAPI Changelog
+## 2.11.10
+
+* [#22932](https://github.com/qbittorrent/qBittorrent/pull/22932)
+ * `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined`
+
## 2.11.9
* [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015)
diff --git a/src/base/bittorrent/categoryoptions.cpp b/src/base/bittorrent/categoryoptions.cpp
index fec609ff7..c839670c6 100644
--- a/src/base/bittorrent/categoryoptions.cpp
+++ b/src/base/bittorrent/categoryoptions.cpp
@@ -52,7 +52,7 @@ BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObj
QJsonObject BitTorrent::CategoryOptions::toJSON() const
{
- QJsonValue downloadPathValue = QJsonValue::Undefined;
+ QJsonValue downloadPathValue = QJsonValue::Null;
if (downloadPath)
{
if (downloadPath->enabled)
diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h
index 6ceb28593..2098e3635 100644
--- a/src/webui/webapplication.h
+++ b/src/webui/webapplication.h
@@ -53,7 +53,7 @@
#include "base/utils/version.h"
#include "api/isessionmanager.h"
-inline const Utils::Version<3, 2> API_VERSION {2, 11, 9};
+inline const Utils::Version<3, 2> API_VERSION {2, 11, 10};
class APIController;
class AuthController;
diff --git a/src/webui/www/private/newcategory.html b/src/webui/www/private/newcategory.html
index 3abdc6f2a..91cd22e02 100644
--- a/src/webui/www/private/newcategory.html
+++ b/src/webui/www/private/newcategory.html
@@ -3,7 +3,7 @@
- QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]
+ QBT_TR(New Category)QBT_TR[CONTEXT=Category]
@@ -26,27 +26,78 @@
}
});
+ const defaultDownloadPath = () => {
+ const category = document.getElementById("categoryName").value.trim();
+ return `${pref.temp_path}/${category}`;
+ };
+
+ const setDownloadPath = (option) => {
+ const downloadPath = document.getElementById("downloadPath");
+ const defaultPath = defaultDownloadPath();
+ switch (option) {
+ case "default":
+ downloadPath.disabled = true;
+ downloadPath.value = pref.temp_path_enabled ? defaultPath : "";
+ downloadPath.placeholder = "";
+ break;
+ case "yes":
+ downloadPath.disabled = false;
+ if ((categoryData !== undefined) && (categoryData.downloadPath !== false) && (categoryData.downloadPath !== null))
+ downloadPath.value = categoryData.downloadPath;
+ else
+ downloadPath.value = "";
+ downloadPath.placeholder = defaultPath;
+ break;
+ case "no":
+ downloadPath.disabled = true;
+ downloadPath.value = "";
+ downloadPath.placeholder = "";
+ break;
+ }
+ };
+
const searchParams = new URLSearchParams(window.location.search);
const uriAction = window.qBittorrent.Misc.safeTrim(searchParams.get("action"));
const uriHashes = window.qBittorrent.Misc.safeTrim(searchParams.get("hashes"));
const uriCategoryName = window.qBittorrent.Misc.safeTrim(searchParams.get("categoryName"));
- const uriSavePath = window.qBittorrent.Misc.safeTrim(searchParams.get("savePath"));
+ const pref = window.parent.qBittorrent.Cache.preferences.get();
+ const categoryData = window.parent.qBittorrent.Client.categoryMap.get(uriCategoryName);
+
+ const useDownloadPathElem = document.getElementById("useDownloadPath");
+ const categoryNameElem = document.getElementById("categoryName");
+ const savePathElem = document.getElementById("savePath");
if (uriAction === "edit") {
if (!uriCategoryName)
return;
- document.getElementById("categoryName").disabled = true;
- document.getElementById("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
- document.getElementById("savePath").value = window.qBittorrent.Misc.escapeHtml(uriSavePath);
- document.getElementById("savePath").focus();
+ categoryNameElem.disabled = true;
+ categoryNameElem.value = uriCategoryName;
+ savePathElem.value = categoryData.savePath;
+ savePathElem.placeholder = `${pref.save_path}/${uriCategoryName}`;
+ savePathElem.focus();
+
+ switch (categoryData.downloadPath) {
+ case false:
+ useDownloadPathElem.selectedIndex = 2;
+ setDownloadPath("no");
+ break;
+ case null:
+ useDownloadPathElem.selectedIndex = 0;
+ setDownloadPath("default");
+ break;
+ default:
+ useDownloadPathElem.selectedIndex = 1;
+ setDownloadPath("yes");
+ break;
+ }
}
else if (uriAction === "createSubcategory") {
- document.getElementById("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
- document.getElementById("categoryName").focus();
+ categoryNameElem.value = uriCategoryName;
+ categoryNameElem.focus();
}
else {
- document.getElementById("categoryName").focus();
+ categoryNameElem.focus();
}
document.getElementById("categoryNameButton").addEventListener("click", (e) => {
@@ -66,6 +117,16 @@
return true;
};
+ const body = {};
+ const useDownloadPath = document.getElementById("useDownloadPath");
+ if (useDownloadPath.value === "no") {
+ body.downloadPathEnabled = false;
+ }
+ else if (useDownloadPath.value === "yes") {
+ body.downloadPathEnabled = true;
+ body.downloadPath = document.getElementById("downloadPath").value.trim();
+ }
+
switch (uriAction) {
case "set":
if ((uriHashes === "") || !verifyCategoryName(categoryName))
@@ -74,6 +135,7 @@
fetch("api/v2/torrents/createCategory", {
method: "POST",
body: new URLSearchParams({
+ ...body,
category: categoryName,
savePath: savePath
})
@@ -110,6 +172,7 @@
fetch("api/v2/torrents/createCategory", {
method: "POST",
body: new URLSearchParams({
+ ...body,
category: categoryName,
savePath: savePath
})
@@ -129,6 +192,7 @@
fetch("api/v2/torrents/editCategory", {
method: "POST",
body: new URLSearchParams({
+ ...body,
category: uriCategoryName, // category name can't be changed
savePath: savePath
})
@@ -146,6 +210,10 @@
}
});
+ useDownloadPathElem.addEventListener("change", (e) => {
+ setDownloadPath(e.target.value);
+ });
+
window.qBittorrent.pathAutofill.attachPathAutofill();
});
@@ -153,10 +221,32 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js
index 01f29e25f..59462273e 100644
--- a/src/webui/www/private/scripts/client.js
+++ b/src/webui/www/private/scripts/client.js
@@ -42,10 +42,17 @@ window.qBittorrent.Client ??= (() => {
showLogViewer: showLogViewer,
isShowSearchEngine: isShowSearchEngine,
isShowRssReader: isShowRssReader,
- isShowLogViewer: isShowLogViewer
+ isShowLogViewer: isShowLogViewer,
+ categoryMap: categoryMap,
+ tagMap: tagMap
};
};
+ // Map
+ const categoryMap = new Map();
+ // Map
+ const tagMap = new Map();
+
let cacheAllSettled;
const setup = () => {
// fetch various data and store it in memory
@@ -146,9 +153,6 @@ const displayFullURLTrackerColumn = LocalPreferences.get("full_url_tracker_colum
const CATEGORIES_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
const CATEGORIES_UNCATEGORIZED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0";
-// Map
-const categoryMap = new Map();
-
let selectedCategory = LocalPreferences.get("selected_category", CATEGORIES_ALL);
let setCategoryFilter = () => {};
@@ -156,9 +160,6 @@ let setCategoryFilter = () => {};
const TAGS_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
const TAGS_UNTAGGED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0";
-// Map
-const tagMap = new Map();
-
let selectedTag = LocalPreferences.get("selected_tag", TAGS_ALL);
let setTagFilter = () => {};
@@ -389,7 +390,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
return false;
let removed = false;
- for (const data of categoryMap.values()) {
+ for (const data of window.qBittorrent.Client.categoryMap.values()) {
const deleteResult = data.torrents.delete(hash);
removed ||= deleteResult;
}
@@ -407,12 +408,12 @@ window.addEventListener("DOMContentLoaded", (event) => {
return true;
}
- let categoryData = categoryMap.get(category);
+ let categoryData = window.qBittorrent.Client.categoryMap.get(category);
if (categoryData === undefined) { // This should not happen
categoryData = {
torrents: new Set()
};
- categoryMap.set(category, categoryData);
+ window.qBittorrent.Client.categoryMap.set(category, categoryData);
}
if (categoryData.torrents.has(hash))
@@ -428,7 +429,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
return false;
let removed = false;
- for (const torrents of tagMap.values()) {
+ for (const torrents of window.qBittorrent.Client.tagMap.values()) {
const deleteResult = torrents.delete(hash);
removed ||= deleteResult;
}
@@ -448,10 +449,10 @@ window.addEventListener("DOMContentLoaded", (event) => {
const tags = torrent["tags"].split(", ");
let added = false;
for (const tag of tags) {
- let torrents = tagMap.get(tag);
+ let torrents = window.qBittorrent.Client.tagMap.get(tag);
if (torrents === undefined) { // This should not happen
torrents = new Set();
- tagMap.set(tag, torrents);
+ window.qBittorrent.Client.tagMap.set(tag, torrents);
}
if (!torrents.has(hash)) {
@@ -550,7 +551,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
}
const sortedCategories = [];
- for (const [category, categoryData] of categoryMap) {
+ for (const [category, categoryData] of window.qBittorrent.Client.categoryMap) {
sortedCategories.push({
categoryName: category,
categoryCount: categoryData.torrents.size,
@@ -651,7 +652,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
tagFilterList.appendChild(createLink(TAGS_UNTAGGED, "QBT_TR(Untagged)QBT_TR[CONTEXT=TagFilterModel]", untagged));
const sortedTags = [];
- for (const [tag, torrents] of tagMap) {
+ for (const [tag, torrents] of window.qBittorrent.Client.tagMap) {
sortedTags.push({
tagName: tag,
tagSize: torrents.size
@@ -815,8 +816,8 @@ window.addEventListener("DOMContentLoaded", (event) => {
updateTrackers = true;
updateTorrents = true;
torrentsTable.clear();
- categoryMap.clear();
- tagMap.clear();
+ window.qBittorrent.Client.categoryMap.clear();
+ window.qBittorrent.Client.tagMap.clear();
trackerMap.clear();
}
if (responseJSON["rid"])
@@ -827,35 +828,38 @@ window.addEventListener("DOMContentLoaded", (event) => {
continue;
const responseData = responseJSON["categories"][responseName];
- const categoryData = categoryMap.get(responseName);
+ const categoryData = window.qBittorrent.Client.categoryMap.get(responseName);
if (categoryData === undefined) {
- categoryMap.set(responseName, {
+ window.qBittorrent.Client.categoryMap.set(responseName, {
savePath: responseData.savePath,
+ downloadPath: responseData.download_path ?? null,
torrents: new Set()
});
}
else {
- // only the save path can change for existing categories
- categoryData.savePath = responseData.savePath;
+ if (responseData.savePath !== undefined)
+ categoryData.savePath = responseData.savePath;
+ if (responseData.download_path !== undefined)
+ categoryData.downloadPath = responseData.download_path;
}
}
updateCategories = true;
}
if (responseJSON["categories_removed"]) {
for (const category of responseJSON["categories_removed"])
- categoryMap.delete(category);
+ window.qBittorrent.Client.categoryMap.delete(category);
updateCategories = true;
}
if (responseJSON["tags"]) {
for (const tag of responseJSON["tags"]) {
- if (!tagMap.has(tag))
- tagMap.set(tag, new Set());
+ if (!window.qBittorrent.Client.tagMap.has(tag))
+ window.qBittorrent.Client.tagMap.set(tag, new Set());
}
updateTags = true;
}
if (responseJSON["tags_removed"]) {
for (const tag of responseJSON["tags_removed"])
- tagMap.delete(tag);
+ window.qBittorrent.Client.tagMap.delete(tag);
updateTags = true;
}
if (responseJSON["trackers"]) {
@@ -945,11 +949,11 @@ window.addEventListener("DOMContentLoaded", (event) => {
if (updateCategories) {
updateCategoryList();
- window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(categoryMap);
+ window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(window.qBittorrent.Client.categoryMap);
}
if (updateTags) {
updateTagList();
- window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagMap);
+ window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(window.qBittorrent.Client.tagMap);
}
if (updateTrackers)
updateTrackerList();
diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js
index 687a8c1cf..f3c7a693c 100644
--- a/src/webui/www/private/scripts/contextmenu.js
+++ b/src/webui/www/private/scripts/contextmenu.js
@@ -449,7 +449,7 @@ window.qBittorrent.ContextMenu ??= (() => {
this.setEnabled("copyInfohash2", thereAreV2Hashes);
const contextTagList = document.getElementById("contextTagList");
- for (const tag of tagMap.keys()) {
+ for (const tag of window.qBittorrent.Client.tagMap.keys()) {
const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag}"] input[type="checkbox"]`);
const count = tagCount.get(tag);
const hasCount = (count !== undefined);
@@ -459,7 +459,7 @@ window.qBittorrent.ContextMenu ??= (() => {
}
const contextCategoryList = document.getElementById("contextCategoryList");
- for (const category of categoryMap.keys()) {
+ for (const category of window.qBittorrent.Client.categoryMap.keys()) {
const categoryIcon = contextCategoryList.querySelector(`a[href$="#Category/${category}"] img`);
const count = categoryCount.get(category);
const isEqual = ((count !== undefined) && (count === selectedRows.length));
diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js
index 032a79a1e..e09a60be9 100644
--- a/src/webui/www/private/scripts/dynamicTable.js
+++ b/src/webui/www/private/scripts/dynamicTable.js
@@ -1630,7 +1630,7 @@ window.qBittorrent.DynamicTable ??= (() => {
return false;
}
else {
- const selectedCategory = categoryMap.get(category);
+ const selectedCategory = window.qBittorrent.Client.categoryMap.get(category);
if (selectedCategory !== undefined) {
const selectedCategoryName = `${category}/`;
const torrentCategoryName = `${row["full_data"].category}/`;
diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js
index 9e358f442..7ea2bb13d 100644
--- a/src/webui/www/private/scripts/mocha-init.js
+++ b/src/webui/www/private/scripts/mocha-init.js
@@ -869,7 +869,7 @@ const initializeWindows = () => {
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
- height: 150
+ height: 200
});
};
@@ -910,7 +910,7 @@ const initializeWindows = () => {
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
- height: 150
+ height: 200
});
};
@@ -932,7 +932,7 @@ const initializeWindows = () => {
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
- height: 150
+ height: 200
});
};
@@ -940,8 +940,7 @@ const initializeWindows = () => {
const contentURL = new URL("newcategory.html", window.location);
contentURL.search = new URLSearchParams({
action: "edit",
- categoryName: category,
- savePath: categoryMap.get(category).savePath
+ categoryName: category
});
new MochaUI.Window({
id: "editCategoryPage",
@@ -955,7 +954,7 @@ const initializeWindows = () => {
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
- height: 150
+ height: 200
});
};
@@ -977,7 +976,7 @@ const initializeWindows = () => {
deleteUnusedCategoriesFN = () => {
const categories = [];
- for (const category of categoryMap.keys()) {
+ for (const category of window.qBittorrent.Client.categoryMap.keys()) {
if (torrentsTable.getFilteredTorrentsNumber("all", category, TAGS_ALL, TRACKERS_ALL) === 0)
categories.push(category);
}
@@ -1082,7 +1081,7 @@ const initializeWindows = () => {
deleteUnusedTagsFN = () => {
const tags = [];
- for (const tag of tagMap.keys()) {
+ for (const tag of window.qBittorrent.Client.tagMap.keys()) {
if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, tag, TRACKERS_ALL) === 0)
tags.push(tag);
}