How qBittorrent Manages Torrent Save Paths by Category: Implementation in SessionImpl
qBittorrent resolves torrent save locations through a hierarchical category system where each category stores an optional savePath in CategoryOptions, falling back to implicit sub-category names or parent category paths when undefined, ultimately defaulting to the global session path if no category-specific path exists.
In the qbittorrent/qBittorrent codebase, torrent organization relies heavily on categories to determine where downloaded files are stored on disk. Rather than manually setting locations for every torrent, the application implements a sophisticated path resolution algorithm that leverages category metadata and inheritance patterns. Understanding how qBittorrent manages torrent save paths by category reveals why your files end up in specific directories and how the software handles complex nested category structures.
The CategoryOptions Data Structure
Each category in qBittorrent persists its configuration through the BitTorrent::CategoryOptions structure defined in src/base/bittorrent/categoryoptions.h. This structure contains a savePath member that stores the user-defined destination for torrents belonging to that category.
When you create or edit a category through the GUI (src/gui/torrentcategorydialog.cpp), the application writes to this savePath field. If left empty, the system generates an implicit path based on the category name itself, enabling zero-configuration organization for simple categorization schemes.
Path Resolution Logic in SessionImpl
The core algorithm for determining where a torrent should be saved resides in SessionImpl::categorySavePath() within src/base/bittorrent/sessionimpl.cpp (lines 437-460). This method accepts a category name and its associated CategoryOptions, then builds the final absolute path through the following logic:
-
Base Path Initialization: The function starts with the global session save path obtained via
SessionImpl::savePath(). -
Empty Category Handling: If the category name is empty, the function immediately returns the global save path, ensuring uncategorized torrents use the default location.
-
Explicit vs Implicit Paths: If
options.savePathcontains a value, that path is used; otherwise, the system treats the sub-category name as an implicit relative path. -
Path Composition: The final resolution follows the expression
path.isAbsolute() ? path : (basePath / path). This means absolute paths override the base directory, while relative paths append to the parent category's or session's base path.
// Conceptual representation of the resolution logic
Path SessionImpl::categorySavePath(const QString &categoryName, const CategoryOptions &options)
{
if (categoryName.isEmpty())
return savePath(); // Global session path
Path basePath = categorySavePath(parentCategoryName(categoryName)); // Recursive inheritance
Path path = options.savePath.isEmpty()
? Path(categoryName) // Implicit path from name
: options.savePath;
return path.isAbsolute() ? path : (basePath / path);
}
Hierarchical Inheritance for Sub-Categories
qBittorrent supports nested categories using a forward-slash delimiter (e.g., "Movies/HD"). When resolving paths for sub-categories, SessionImpl::categorySavePath() recursively calls itself with parentCategoryName(categoryName) to traverse the hierarchy upward.
This recursive approach ensures that child categories inherit their parent's save path unless they explicitly define their own. A torrent in "Movies/HD" inherits the path from "Movies" unless "HD" specifies a custom location, creating a cascading configuration system that minimizes redundant path entries.
Automatic Torrent Management (Auto-TMM) Interactions
When category save paths change, qBittorrent must reconcile existing torrent locations with the new configuration. In SessionImpl::setCategoryOptions(), the application may disable Automatic Torrent Management (Auto-TMM) for all torrents within the affected category.
This safety mechanism prevents unexpected file moves that could occur if the system automatically relocated hundreds of torrents to a new directory immediately after a category path change. Users must explicitly re-enable Auto-TMM if they want the application to manage those torrents under the new path structure.
UI and API Integration
The save path resolution permeates both the desktop interface and the Web API. In src/gui/torrentoptionsdialog.cpp, the dialog pre-populates the "Save path" field by calling btSession->categorySavePath(m_ui->comboCategory->currentText()), ensuring users see the effective destination before adding a torrent.
The Web API implementation in src/webui/api/torrentscontroller.cpp exposes this functionality through endpoints that report a torrent's savePath and allow per-torrent overrides. However, new torrents without explicit path assignments default to the category's resolved path via the same categorySavePath() mechanism.
Practical Code Examples
To retrieve the effective save path for a category in your own qBittorrent plugins or modifications:
// Resolve the save path for a nested category
BitTorrent::Session *session = BitTorrent::Session::instance();
QString category = "Movies/HD";
Path effectivePath = session->categorySavePath(category);
// effectivePath now contains the absolute directory (e.g., /mnt/storage/Movies/HD)
When implementing category path updates via the WebUI controller pattern:
// Setting a new save path for a category
QJsonObject payload;
payload["savePath"] = "/mnt/media/Movies";
auto *controller = new TorrentsController(...);
controller->setCategoryOptions("Movies", payload);
// Existing torrents may have Auto-TMM disabled by this operation
For GUI development, pre-selecting the appropriate path based on category selection:
// In torrentoptionsdialog.cpp context
const Path savePath = btSession->categorySavePath(m_ui->comboCategory->currentText());
m_ui->savePath->setSelectedPath(savePath);
Summary
- CategoryOptions stores explicit save paths in
src/base/bittorrent/categoryoptions.h, with empty values triggering implicit path generation. - SessionImpl::categorySavePath() implements hierarchical resolution in
src/base/bittorrent/sessionimpl.cpp, recursively traversing parent categories to build the final path. - Path composition uses absolute path checks (
path.isAbsolute()) to determine whether to append to the base path or use the specified location directly. - Auto-TMM protection may disable automatic management when category paths change to prevent unintended bulk file moves.
- UI and API layers consume the same base function, ensuring consistent path resolution across the Qt GUI (
torrentoptionsdialog.cpp) and Web API (torrentscontroller.cpp).
Frequently Asked Questions
How does qBittorrent handle relative paths in category save locations?
Relative paths append to the parent category's path or the global session save path according to the logic path.isAbsolute() ? path : (basePath / path) in SessionImpl::categorySavePath(). If you specify "Downloads" as a relative path for a category, and the base path is /home/user, the effective path becomes /home/user/Downloads.
What happens to existing torrents when I change a category's save path?
When modifying category options through SessionImpl::setCategoryOptions(), qBittorrent may disable Auto-TMM for all torrents in that category to prevent immediate, potentially destructive file moves. The files remain in their current locations until you manually move them or re-enable automatic management.
How does qBittorrent determine the save path for uncategorized torrents?
Uncategorized torrents (empty category name) resolve to the global session save path returned by SessionImpl::savePath(). The categorySavePath() function checks for empty category names first and returns the base session path before evaluating any category-specific logic.
Can sub-categories override parent category paths while maintaining the hierarchy?
Yes. Sub-categories inherit their parent's save path recursively unless they define their own savePath in CategoryOptions. For a category "Movies/HD", the system first resolves "Movies", then checks if "HD" has an explicit path. If not, it uses "HD" as an implicit relative path appended to the "Movies" directory.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →