Compare commits

...

4 Commits

Author SHA1 Message Date
Mark Yu
5edaf2cf10 Use class instead of struct
PR #23255.
2025-09-14 17:18:44 +08:00
Mark Yu
fcaa95101d Allow equals character in the command line value
The CLI options should allow the `=` (equals) char as value so doing `--save-path="/home/test/mydir = somedir"` should parse properly with the `value` method returning `"/home/test/mydir = somedir"`.

Closes #23248.
PR #23251.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2025-09-14 16:54:14 +08:00
Mark Yu
8e14541236 Allow to copy content paths of selected torrents
Closes #23227.
PR #23239.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2025-09-14 16:42:59 +08:00
xavier2k6
919520b4c3 GHA CI: Install NSIS via third party action on Windows
* Install `NSIS` via https://github.com/repolevedavaj/install-nsis
* `NSIS` is no longer installed on GitHub provided Runner Image as of `windows-2025`
* `NSIS: 3.10` was only available on `windows-2019 / windows-2022`
* We can use newer release now eg. `NSIS: 3.11`.

PR #23249.
2025-09-14 16:33:24 +08:00
7 changed files with 49 additions and 6 deletions

View File

@@ -199,6 +199,11 @@ jobs:
name: qBittorrent-CI_Windows-x64_libtorrent-${{ matrix.libt_version }}
path: upload
- name: Install NSIS
uses: repolevedavaj/install-nsis@265e893c16602d8ccfb0a9ca44173b084078917c # v1.0.3
with:
nsis-version: '3.11'
- name: Create installer
run: |
7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip"

View File

@@ -137,7 +137,7 @@ namespace
};
// Option with string value. May not have a shortcut
struct StringOption : protected Option
class StringOption : protected Option
{
public:
explicit constexpr StringOption(const QStringView name)
@@ -147,12 +147,14 @@ namespace
QString value(const QString &arg) const
{
QStringList parts = arg.split(u'=');
if (parts.size() == 2)
return Utils::String::unquote(parts[1], u"'\""_s);
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
const qsizetype index = arg.indexOf(u'=');
if (index == -1)
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
.arg(fullParameter(), u"<value>"_s));
const QStringView val = QStringView(arg).sliced(index + 1);
return Utils::String::unquote(val, u"'\""_s).toString();
}
QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
@@ -168,7 +170,7 @@ namespace
friend bool operator==(const StringOption &option, const QString &arg)
{
return arg.startsWith(option.parameterAssignment());
return (arg == option.fullParameter()) || arg.startsWith(option.parameterAssignment());
}
private:

View File

@@ -525,6 +525,19 @@ void TransferListWidget::copySelectedNames() const
qApp->clipboard()->setText(torrentNames.join(u'\n'));
}
void TransferListWidget::copyContentPaths() const
{
QStringList contentPaths;
for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents()))
{
const Path contentPath = torrent->contentPath();
if (!contentPath.isEmpty())
contentPaths << contentPath.toString();
}
qApp->clipboard()->setText(contentPaths.join(u'\n'));
}
void TransferListWidget::copySelectedInfohashes(const CopyInfohashPolicy policy) const
{
const auto selectedTorrents = getSelectedTorrents();
@@ -1000,6 +1013,8 @@ void TransferListWidget::displayListMenu()
connect(actionCopyHash1, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version1); });
auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_s, u"edit-copy"_s), tr("Info h&ash v2"), listMenu);
connect(actionCopyHash2, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version2); });
auto *actionCopyContentPath = new QAction(UIThemeManager::instance()->getIcon(u"directory"_s, u"edit-copy"_s), tr("Content &Path"), listMenu);
connect(actionCopyContentPath, &QAction::triggered, this, &TransferListWidget::copyContentPaths);
auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu);
connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding);
auto *actionRename = new QAction(UIThemeManager::instance()->getIcon(u"edit-rename"_s), tr("Re&name..."), listMenu);
@@ -1282,6 +1297,7 @@ void TransferListWidget::displayListMenu()
copySubMenu->addAction(actionCopyMagnetLink);
copySubMenu->addAction(actionCopyID);
copySubMenu->addAction(actionCopyComment);
copySubMenu->addAction(actionCopyContentPath);
actionExportTorrent->setToolTip(tr("Exported torrent is not necessarily the same as the imported"));
listMenu->addAction(actionExportTorrent);

View File

@@ -85,6 +85,7 @@ public slots:
void bottomQueuePosSelectedTorrents();
void copySelectedMagnetURIs() const;
void copySelectedNames() const;
void copyContentPaths() const;
void copySelectedInfohashes(CopyInfohashPolicy policy) const;
void copySelectedIDs() const;
void copySelectedComments() const;

View File

@@ -219,6 +219,7 @@
<li><a href="#" id="copyMagnetLink" class="copyToClipboard"><img src="images/torrent-magnet.svg" alt="QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]"> QBT_TR(Magnet link)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyID" class="copyToClipboard"><img src="images/help-about.svg" alt="QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget]"> QBT_TR(Torrent ID)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyComment" class="copyToClipboard"><img src="images/edit-copy.svg" alt="QBT_TR(Comment)QBT_TR[CONTEXT=TransferListWidget]"> QBT_TR(Comment)QBT_TR[CONTEXT=TransferListWidget]</a></li>
<li><a href="#" id="copyContentPath" class="copyToClipboard"><img src="images/directory.svg" alt="QBT_TR(Content Path)QBT_TR[CONTEXT=TransferListWidget]"> QBT_TR(Content Path)QBT_TR[CONTEXT=TransferListWidget]</a></li>
</ul>
</li>
<li>

View File

@@ -1886,6 +1886,9 @@ window.addEventListener("DOMContentLoaded", (event) => {
case "copyComment":
setupClickEvent(copyCommentFN);
break;
case "copyContentPath":
setupClickEvent(copyContentPathFN);
break;
}
}

View File

@@ -153,6 +153,7 @@ let copyInfohashFN = (policy) => {};
let copyMagnetLinkFN = () => {};
let copyIdFN = () => {};
let copyCommentFN = () => {};
let copyContentPathFN = () => {};
let setQueuePositionFN = () => {};
let exportTorrentFN = () => {};
@@ -1219,6 +1220,20 @@ const initializeWindows = () => {
return comments.join("\n---------\n");
};
copyContentPathFN = () => {
const selectedRows = torrentsTable.selectedRowsIds();
const contentPaths = [];
if (selectedRows.length > 0) {
const rows = torrentsTable.getFilteredAndSortedRows();
for (const hash of selectedRows) {
const contentPath = rows[hash].full_data.content_path;
if ((contentPath !== null) && (contentPath.length > 0))
contentPaths.push(contentPath);
}
}
return contentPaths.join("\n");
};
exportTorrentFN = async () => {
const hashes = torrentsTable.selectedRowsIds();
for (const hash of hashes) {