Skip to content

Commit 28a2f73

Browse files
committed
WebAPI: Support persisting WebUI client preferences
This provides a mechanism for persisting WebUI client preferences that are distinct from the broader qBittorrent preferences. These preferences apply exclusively to the WebUI.
1 parent 93a7267 commit 28a2f73

File tree

8 files changed

+339
-0
lines changed

8 files changed

+339
-0
lines changed

WebAPI_Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
55
* `torrents/add` endpoint now supports downloading from a search plugin via the `downloader` parameter
66
* `torrents/fetchMetadata` endpoint now supports fetching from a search plugin via the `downloader` parameter
7+
* [#23088](https://github.com/qbittorrent/qBittorrent/pull/23088)
8+
* Add `clientdata/load` and `clientdata/store` endpoints for managing WebUI-specific client settings and other shared data
79

810
## 2.13.0
911
* [#23045](https://github.com/qbittorrent/qBittorrent/pull/23045)

src/webui/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_library(qbt_webui STATIC
55
api/apistatus.h
66
api/appcontroller.h
77
api/authcontroller.h
8+
api/clientdatacontroller.h
89
api/isessionmanager.h
910
api/logcontroller.h
1011
api/rsscontroller.h
@@ -14,6 +15,7 @@ add_library(qbt_webui STATIC
1415
api/torrentscontroller.h
1516
api/transfercontroller.h
1617
api/serialize/serialize_torrent.h
18+
clientdatastorage.h
1719
webapplication.h
1820
webui.h
1921

@@ -22,6 +24,7 @@ add_library(qbt_webui STATIC
2224
api/apierror.cpp
2325
api/appcontroller.cpp
2426
api/authcontroller.cpp
27+
api/clientdatacontroller.cpp
2528
api/logcontroller.cpp
2629
api/rsscontroller.cpp
2730
api/searchcontroller.cpp
@@ -30,6 +33,7 @@ add_library(qbt_webui STATIC
3033
api/torrentscontroller.cpp
3134
api/transfercontroller.cpp
3235
api/serialize/serialize_torrent.cpp
36+
clientdatastorage.cpp
3337
webapplication.cpp
3438
webui.cpp
3539
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Bittorrent Client using Qt and libtorrent.
3+
* Copyright (C) 2025 Thomas Piccirello <[email protected]>
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
*
19+
* In addition, as a special exception, the copyright holders give permission to
20+
* link this program with the OpenSSL project's "OpenSSL" library (or with
21+
* modified versions of it that use the same license as the "OpenSSL" library),
22+
* and distribute the linked executables. You must obey the GNU General Public
23+
* License in all respects for all of the code used other than "OpenSSL". If you
24+
* modify file(s), you may extend this exception to your version of the file(s),
25+
* but you are not obligated to do so. If you do not wish to do so, delete this
26+
* exception statement from your version.
27+
*/
28+
29+
#include "clientdatacontroller.h"
30+
31+
#include <QJsonArray>
32+
#include <QJsonDocument>
33+
#include <QJsonObject>
34+
35+
#include "base/global.h"
36+
#include "base/interfaces/iapplication.h"
37+
#include "base/logger.h"
38+
#include "apierror.h"
39+
#include "webui/clientdatastorage.h"
40+
41+
ClientDataController::ClientDataController(ClientDataStorage *clientDataStorage, IApplication *app, QObject *parent)
42+
: APIController(app, parent)
43+
, m_clientDataStorage {clientDataStorage}
44+
{
45+
}
46+
47+
void ClientDataController::loadAction()
48+
{
49+
const QString keysParam {params()[u"keys"_s]};
50+
if (keysParam.isEmpty())
51+
{
52+
setResult(m_clientDataStorage->loadData());
53+
return;
54+
}
55+
56+
QJsonParseError jsonError;
57+
const auto keysJsonDocument = QJsonDocument::fromJson(keysParam.toUtf8(), &jsonError);
58+
if (jsonError.error != QJsonParseError::NoError)
59+
throw APIError(APIErrorType::BadParams, jsonError.errorString());
60+
if (!keysJsonDocument.isArray())
61+
throw APIError(APIErrorType::BadParams, tr("`keys` must be an array"));
62+
63+
QStringList keys;
64+
for (const QJsonValue &keysJsonVal : asConst(keysJsonDocument.array()))
65+
{
66+
if (!keysJsonVal.isString())
67+
throw APIError(APIErrorType::BadParams, tr("Items of `keys` must be strings"));
68+
69+
keys << keysJsonVal.toString();
70+
}
71+
72+
setResult(m_clientDataStorage->loadData(keys));
73+
}
74+
75+
void ClientDataController::storeAction()
76+
{
77+
requireParams({u"data"_s});
78+
QJsonParseError jsonError;
79+
const auto dataJsonDocument = QJsonDocument::fromJson(params()[u"data"_s].toUtf8(), &jsonError);
80+
if (jsonError.error != QJsonParseError::NoError)
81+
throw APIError(APIErrorType::BadParams, jsonError.errorString());
82+
if (!dataJsonDocument.isObject())
83+
throw APIError(APIErrorType::BadParams, tr("`data` must be an object"));
84+
85+
const nonstd::expected<void, QString> result = m_clientDataStorage->storeData(dataJsonDocument.object());
86+
if (!result)
87+
throw APIError(APIErrorType::Conflict, result.error());
88+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Bittorrent Client using Qt and libtorrent.
3+
* Copyright (C) 2025 Thomas Piccirello <[email protected]>
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
*
19+
* In addition, as a special exception, the copyright holders give permission to
20+
* link this program with the OpenSSL project's "OpenSSL" library (or with
21+
* modified versions of it that use the same license as the "OpenSSL" library),
22+
* and distribute the linked executables. You must obey the GNU General Public
23+
* License in all respects for all of the code used other than "OpenSSL". If you
24+
* modify file(s), you may extend this exception to your version of the file(s),
25+
* but you are not obligated to do so. If you do not wish to do so, delete this
26+
* exception statement from your version.
27+
*/
28+
29+
#pragma once
30+
31+
#include "apicontroller.h"
32+
33+
class ClientDataStorage;
34+
35+
class ClientDataController final : public APIController
36+
{
37+
Q_OBJECT
38+
Q_DISABLE_COPY_MOVE(ClientDataController)
39+
40+
public:
41+
ClientDataController(ClientDataStorage *clientDataStorage, IApplication *app, QObject *parent = nullptr);
42+
43+
private slots:
44+
void loadAction();
45+
void storeAction();
46+
47+
private:
48+
ClientDataStorage *m_clientDataStorage = nullptr;
49+
};

src/webui/clientdatastorage.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Bittorrent Client using Qt and libtorrent.
3+
* Copyright (C) 2025 Thomas Piccirello <[email protected]>
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
*
19+
* In addition, as a special exception, the copyright holders give permission to
20+
* link this program with the OpenSSL project's "OpenSSL" library (or with
21+
* modified versions of it that use the same license as the "OpenSSL" library),
22+
* and distribute the linked executables. You must obey the GNU General Public
23+
* License in all respects for all of the code used other than "OpenSSL". If you
24+
* modify file(s), you may extend this exception to your version of the file(s),
25+
* but you are not obligated to do so. If you do not wish to do so, delete this
26+
* exception statement from your version.
27+
*/
28+
29+
#include "clientdatastorage.h"
30+
31+
#include <QJsonDocument>
32+
#include <QJsonObject>
33+
34+
#include "base/global.h"
35+
#include "base/logger.h"
36+
#include "base/path.h"
37+
#include "base/profile.h"
38+
#include "base/utils/io.h"
39+
40+
const int CLIENT_DATA_FILE_MAX_SIZE = 1024 * 1024; // 1 MiB
41+
const QString CLIENT_DATA_FILE_NAME = u"web_clientdata.json"_s;
42+
43+
ClientDataStorage::ClientDataStorage(QObject *parent)
44+
: QObject(parent)
45+
, m_clientDataFilePath(specialFolderLocation(SpecialFolder::Data) / Path(CLIENT_DATA_FILE_NAME))
46+
{
47+
if (!m_clientDataFilePath.exists())
48+
return;
49+
50+
const auto readResult = Utils::IO::readFile(m_clientDataFilePath, CLIENT_DATA_FILE_MAX_SIZE);
51+
if (!readResult)
52+
{
53+
LogMsg(tr("Failed to load web client data. %1").arg(readResult.error().message), Log::WARNING);
54+
return;
55+
}
56+
57+
QJsonParseError jsonError;
58+
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
59+
if (jsonError.error != QJsonParseError::NoError)
60+
{
61+
LogMsg(tr("Failed to parse web client data. File: \"%1\". Error: \"%2\"")
62+
.arg(m_clientDataFilePath.toString(), jsonError.errorString()), Log::WARNING);
63+
return;
64+
}
65+
66+
if (!jsonDoc.isObject())
67+
{
68+
LogMsg(tr("Failed to load web client data. File: \"%1\". Error: \"Invalid data format\"")
69+
.arg(m_clientDataFilePath.toString()), Log::WARNING);
70+
return;
71+
}
72+
73+
m_clientData = jsonDoc.object();
74+
}
75+
76+
nonstd::expected<void, QString> ClientDataStorage::storeData(const QJsonObject &object)
77+
{
78+
QJsonObject clientData = m_clientData;
79+
bool dataModified = false;
80+
for (auto it = object.constBegin(), end = object.constEnd(); it != end; ++it)
81+
{
82+
const QString &key = it.key();
83+
const QJsonValue &value = it.value();
84+
85+
if (value.isNull())
86+
{
87+
if (auto it = clientData.find(key); it != clientData.end())
88+
{
89+
clientData.erase(it);
90+
dataModified = true;
91+
}
92+
}
93+
else
94+
{
95+
const auto &existingValue = clientData.find(key);
96+
if (existingValue == clientData.end())
97+
{
98+
clientData.insert(key, value);
99+
dataModified = true;
100+
}
101+
else if (existingValue.value() != value)
102+
{
103+
existingValue.value() = value;
104+
dataModified = true;
105+
}
106+
}
107+
}
108+
109+
if (dataModified)
110+
{
111+
const QByteArray json = QJsonDocument(clientData).toJson(QJsonDocument::Compact);
112+
if (json.size() > CLIENT_DATA_FILE_MAX_SIZE)
113+
return nonstd::make_unexpected(tr("Total web client data must not be larger than %1 bytes").arg(CLIENT_DATA_FILE_MAX_SIZE));
114+
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(m_clientDataFilePath, json);
115+
if (!result)
116+
return nonstd::make_unexpected(tr("Failed to save web client data. Error: \"%1\"").arg(result.error()));
117+
118+
m_clientData = clientData;
119+
}
120+
121+
return {};
122+
}
123+
124+
QJsonObject ClientDataStorage::loadData() const
125+
{
126+
return m_clientData;
127+
}
128+
129+
QJsonObject ClientDataStorage::loadData(const QStringList &keys) const
130+
{
131+
QJsonObject clientData;
132+
for (const QString &key : keys)
133+
{
134+
if (const auto iter = m_clientData.constFind(key); iter != m_clientData.constEnd())
135+
clientData.insert(key, iter.value());
136+
}
137+
return clientData;
138+
}

src/webui/clientdatastorage.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Bittorrent Client using Qt and libtorrent.
3+
* Copyright (C) 2025 Thomas Piccirello <[email protected]>
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
*
19+
* In addition, as a special exception, the copyright holders give permission to
20+
* link this program with the OpenSSL project's "OpenSSL" library (or with
21+
* modified versions of it that use the same license as the "OpenSSL" library),
22+
* and distribute the linked executables. You must obey the GNU General Public
23+
* License in all respects for all of the code used other than "OpenSSL". If you
24+
* modify file(s), you may extend this exception to your version of the file(s),
25+
* but you are not obligated to do so. If you do not wish to do so, delete this
26+
* exception statement from your version.
27+
*/
28+
29+
#pragma once
30+
31+
#include <QJsonObject>
32+
33+
#include "base/3rdparty/expected.hpp"
34+
#include "base/path.h"
35+
36+
class ClientDataStorage final : public QObject
37+
{
38+
Q_OBJECT
39+
Q_DISABLE_COPY_MOVE(ClientDataStorage)
40+
41+
public:
42+
ClientDataStorage(QObject *parent = nullptr);
43+
44+
nonstd::expected<void, QString> storeData(const QJsonObject &object);
45+
QJsonObject loadData() const;
46+
QJsonObject loadData(const QStringList &keys) const;
47+
48+
private:
49+
Path m_clientDataFilePath;
50+
QJsonObject m_clientData;
51+
};

0 commit comments

Comments
 (0)