# How qBittorrent Saves and Loads Torrent Resume Data: Architecture and Implementation

> Discover how qBittorrent saves and loads torrent resume data using legacy fastresume files or experimental SQLite. Explore the architecture and implementation in src/base/bittorrent.

- Repository: [qBittorrent project/qBittorrent](https://github.com/qbittorrent/qBittorrent)
- Tags: internals
- Published: 2026-05-05

---

**qBittorrent persists torrent states using either legacy `.fastresume` files or an experimental SQLite database, both managed by the `ResumeDataStorage` abstraction in `src/base/bittorrent/`.**

qBittorrent maintains download progress, file priorities, and share limits across application restarts through a robust resume data system. The architecture centers on an abstract `ResumeDataStorage` class with two concrete implementations: legacy file-based storage and modern SQLite storage. This article examines the internal mechanisms that handle torrent state persistence according to the qbittorrent/qBittorrent source code.

## Resume Data Storage Architecture

### The Abstract ResumeDataStorage Interface

All persistence operations flow through the abstract base class defined in [`src/base/bittorrent/resumedatastorage.h`](https://github.com/qbittorrent/qBittorrent/blob/main/src/base/bittorrent/resumedatastorage.h). This interface declares pure virtual methods `doLoadAll()`, `load()`, and `store()` that concrete implementations must override. The base class handles thread lifecycle management and emits signals like `loadStarted` and `loadFinished` to notify the `SessionImpl` of asynchronous operation progress.

### Legacy vs SQLite Implementations

The application supports two storage backends selected via **Advanced → Network → Resume data storage type**:

- **BencodeResumeDataStorage**: Stores individual `.fastresume` files per torrent in the `dataPath/fastresume` directory, maintaining original qBittorrent behavior.
- **DBResumeDataStorage**: Persists all data in a single SQLite file (`resume.db`) with schema version 10, offering atomic updates and reduced filesystem clutter.

In [`src/base/bittorrent/sessionimpl.cpp`](https://github.com/qbittorrent/qBittorrent/blob/main/src/base/bittorrent/sessionimpl.cpp), the `SessionImpl::init()` method instantiates the appropriate storage based on the `ResumeDataStorageType` configuration:

```cpp
if (type == ResumeDataStorageType::SQLite)
    m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
else
    m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);

```

## How Resume Data Is Loaded on Startup

### Initialization and Asynchronous Loading

When qBittorrent launches, `SessionImpl` triggers `ResumeDataStorage::loadAll()`, which spawns a background thread calling the virtual `doLoadAll()`. The legacy implementation scans the fastresume directory, while the SQLite version queries the `torrents` table, emitting `loadStarted` with the list of torrent IDs before processing begins.

### Parsing libtorrent Resume Blobs

Each storage implementation parses raw bencoded data using libtorrent APIs. In `DBResumeDataStorage::parseQueryResultRow` (found in [`src/base/bittorrent/dbresumedatastorage.cpp`](https://github.com/qbittorrent/qBittorrent/blob/main/src/base/bittorrent/dbresumedatastorage.cpp)), the code decodes the binary blob:

```cpp
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr,
                                                  bdecodeDepthLimit, bdecodeTokenLimit);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);

```

The `BencodeResumeDataStorage::loadTorrentResumeData` method performs equivalent parsing for file-based storage, converting the bencoded entry into `lt::add_torrent_params` for libtorrent session injection.

## How Resume Data Is Saved

### Store Operations and Threading

State changes trigger `ResumeDataStorage::store(const TorrentID&, const LoadTorrentParams&)`. The legacy backend writes `.fastresume` files synchronously on a dedicated I/O thread (`m_ioThread`), while the SQLite backend enqueues `StoreJob` instances to an asynchronous worker thread (`DBResumeDataStorage::Worker`).

The SQLite worker serializes parameters using `lt::write_resume_data()`:

```cpp
lt::entry data = lt::write_resume_data(p);
// ... bencode data into QByteArray ...
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
    query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
query.exec();

```

Metadata extraction occurs similarly in both backends, though SQLite stores it in a dedicated column while legacy storage uses separate `.metadata` files.

### Queue Position Persistence

Torrent queue order is preserved through the `storeQueue(const QList<TorrentID>&)` method. For SQLite, this creates a `StoreQueueJob` that updates the `queue_position` column for each torrent ID. The legacy implementation updates the `queue_position` field within each respective `.fastresume` file.

## Practical Code Examples

### Loading All Torrents at Startup

```cpp
// Assume `session` is a BitTorrent::SessionImpl pointer
session->resumeDataStorage()->loadAll();   // asynchronous, emits signals

connect(session->resumeDataStorage(), &BitTorrent::ResumeDataStorage::loadStarted,
        this, [](const QList<BitTorrent::TorrentID> &ids){
    qDebug() << "Loading resume data for" << ids.size() << "torrents";
});
connect(session->resumeDataStorage(), &BitTorrent::ResumeDataStorage::loadFinished,
        this, [](){ qDebug() << "All resume data loaded"; });

```

### Storing a Single Torrent's State

```cpp
BitTorrent::Torrent *t = session->torrentHandle(torrentID);
LoadTorrentParams resume = t->getResumeData();   // collects libtorrent params + UI fields
session->resumeDataStorage()->store(torrentID, resume);

```

### Updating Queue Order

```cpp
QList<BitTorrent::TorrentID> newQueue = session->queuedTorrentIDs();
session->resumeDataStorage()->storeQueue(newQueue);

```

## Summary

- qBittorrent uses an abstract `ResumeDataStorage` class with two backends: legacy `.fastresume` files (`BencodeResumeDataStorage`) and SQLite (`DBResumeDataStorage`).
- Storage selection occurs in `SessionImpl::init()` via the `ResumeDataStorageType` enum value.
- Resume data loading runs asynchronously in background threads, parsing bencoded blobs through `lt::bdecode` and `lt::read_resume_data`.
- Saving occurs via `store()` methods that use `lt::write_resume_data`, with SQLite operations queued to a dedicated worker thread.
- Queue positions are stored separately through `storeQueue()`, mapping to either database columns or file fields.

## Frequently Asked Questions

### Where does qBittorrent store resume data files?

Legacy storage places individual `.fastresume` files in the `fastresume` subdirectory of the application data path, while the experimental SQLite backend consolidates everything into a single `resume.db` file. Metadata for hybrid torrents may be stored in separate `.metadata` files when using the legacy backend, or in the `metadata` BLOB column when using SQLite.

### What is the difference between BencodeResumeDataStorage and DBResumeDataStorage?

`BencodeResumeDataStorage` maintains one file per torrent using bencoded dictionaries, performing I/O on a dedicated `QThread`. `DBResumeDataStorage` uses a SQLite database with schema version 10, offering atomic transactions and asynchronous writes through a custom `Worker` thread class. The SQLite approach reduces filesystem fragmentation and supports faster batch updates.

### How does qBittorrent handle resume data corruption?

Both implementations rely on libtorrent's `bdecode` with explicit depth and token limits (`bdecodeDepthLimit`, `bdecodeTokenLimit`) to prevent malicious or corrupted data from causing stack exhaustion. The SQLite backend benefits from database integrity checks, while the legacy loader validates file existence and parsing errors before emitting loaded torrents.

### Can I migrate between legacy and SQLite resume storage?

qBittorrent does not provide automatic migration between storage formats. Switching modes in **Advanced → Network** begins using the new format for subsequent operations, but existing data remains in the original format. Users must re-add torrents to populate the new storage type, or manually convert data using external tools that understand both the `.fastresume` bencode structure and the SQLite schema.