From 073ca4267c226afbc1be3e2fa97cf2dd29f0a92f Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 19 Dec 2023 00:08:37 +0800 Subject: [PATCH] Introduce helper function to join values as string PR #20130. --- src/app/application.cpp | 5 +- src/base/CMakeLists.txt | 1 + src/base/bittorrent/dbresumedatastorage.cpp | 2 +- src/base/bittorrent/sessionimpl.cpp | 7 ++- src/base/concepts/explicitlyconvertibleto.h | 35 ++++++++++++ src/base/orderedset.h | 22 -------- src/base/tag.cpp | 1 - src/base/utils/string.cpp | 11 ---- src/base/utils/string.h | 35 ++++++++++-- src/gui/addnewtorrentdialog.cpp | 5 +- src/gui/addtorrentparamswidget.cpp | 5 +- src/gui/transferlistmodel.cpp | 5 +- src/webui/api/serialize/serialize_torrent.cpp | 6 +-- test/CMakeLists.txt | 1 + test/testconceptsexplicitlyconvertibleto.cpp | 54 +++++++++++++++++++ test/testorderedset.cpp | 34 +++++------- test/testutilsstring.cpp | 36 +++++++++++++ 17 files changed, 188 insertions(+), 77 deletions(-) create mode 100644 src/base/concepts/explicitlyconvertibleto.h create mode 100644 test/testconceptsexplicitlyconvertibleto.cpp diff --git a/src/app/application.cpp b/src/app/application.cpp index b700483b5..26669d8e9 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -522,10 +522,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo str.replace(i, 2, torrent->contentPath().toString()); break; case u'G': - { - const TagSet &tags = torrent->tags(); - str.replace(i, 2, QStringList(tags.cbegin(), tags.cend()).join(u","_s)); - } + str.replace(i, 2, Utils::String::joinIntoString(torrent->tags(), u","_s)); break; case u'I': str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_s)); diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index bcfc5f763..4f25f86aa 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(qbt_base STATIC bittorrent/torrentinfo.h bittorrent/tracker.h bittorrent/trackerentry.h + concepts/explicitlyconvertibleto.h concepts/stringable.h digest32.h exceptions.h diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index e68001724..66c6307d0 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -854,7 +854,7 @@ namespace query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name); query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category); query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty() - ? QString() : QStringList(m_resumeData.tags.cbegin(), m_resumeData.tags.cend()).join(u","_s))); + ? QString() : Utils::String::joinIntoString(m_resumeData.tags, u","_s))); query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout)); query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast(m_resumeData.ratioLimit * 1000)); query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit); diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 8de83fdaf..16214272e 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -552,8 +552,11 @@ SessionImpl::SessionImpl(QObject *parent) } const QStringList storedTags = m_storedTags.get(); - m_tags.insert(storedTags.cbegin(), storedTags.cend()); - std::erase_if(m_tags, [](const Tag &tag) { return !tag.isValid(); }); + for (const QString &tagStr : storedTags) + { + if (const Tag tag {tagStr}; tag.isValid()) + m_tags.insert(tag); + } updateSeedingLimitTimer(); populateAdditionalTrackers(); diff --git a/src/base/concepts/explicitlyconvertibleto.h b/src/base/concepts/explicitlyconvertibleto.h new file mode 100644 index 000000000..3ecd70f1c --- /dev/null +++ b/src/base/concepts/explicitlyconvertibleto.h @@ -0,0 +1,35 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +template +concept ExplicitlyConvertibleTo = requires (From f) +{ + static_cast(f); +}; diff --git a/src/base/orderedset.h b/src/base/orderedset.h index f553f833f..229b23bfd 100644 --- a/src/base/orderedset.h +++ b/src/base/orderedset.h @@ -29,12 +29,9 @@ #pragma once -#include #include #include -#include "algorithm.h" - template > class OrderedSet : public std::set { @@ -70,25 +67,6 @@ public: return BaseType::empty(); } - QString join(const QString &separator) const - requires std::same_as - { - auto iter = BaseType::cbegin(); - if (iter == BaseType::cend()) - return {}; - - QString ret = *iter; - ++iter; - - while (iter != BaseType::cend()) - { - ret.push_back(separator + *iter); - ++iter; - } - - return ret; - } - bool remove(const key_type &value) { return (BaseType::erase(value) > 0); diff --git a/src/base/tag.cpp b/src/base/tag.cpp index 2559a512b..06c1efccf 100644 --- a/src/base/tag.cpp +++ b/src/base/tag.cpp @@ -31,7 +31,6 @@ #include #include "base/concepts/stringable.h" -#include "base/global.h" namespace { diff --git a/src/base/utils/string.cpp b/src/base/utils/string.cpp index 4f3cebb9b..0c6491438 100644 --- a/src/base/utils/string.cpp +++ b/src/base/utils/string.cpp @@ -119,14 +119,3 @@ std::optional Utils::String::parseDouble(const QString &string) return std::nullopt; } - -QString Utils::String::join(const QList &strings, const QStringView separator) -{ - if (strings.empty()) - return {}; - - QString ret = strings[0].toString(); - for (int i = 1; i < strings.count(); ++i) - ret += (separator + strings[i]); - return ret; -} diff --git a/src/base/utils/string.h b/src/base/utils/string.h index 00a30cbc1..6a72176f2 100644 --- a/src/base/utils/string.h +++ b/src/base/utils/string.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Mike Tzou (Chocobo1) * Copyright (C) 2015 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * @@ -29,14 +30,15 @@ #pragma once +#include #include #include #include #include -#include #include +#include "base/concepts/explicitlyconvertibleto.h" #include "base/global.h" namespace Utils::String @@ -63,10 +65,37 @@ namespace Utils::String QStringList splitCommand(const QString &command); - QString join(const QList &strings, QStringView separator); - QString fromDouble(double n, int precision); + template + QString joinIntoString(const Container &container, const QString &separator) + requires ExplicitlyConvertibleTo + { + auto iter = container.cbegin(); + const auto end = container.cend(); + if (iter == end) + return {}; + + const qsizetype totalLength = std::accumulate(iter, end, (separator.size() * (container.size() - 1)) + , [](const qsizetype total, const typename Container::value_type &value) + { + return total + QString(value).size(); + }); + + QString ret; + ret.reserve(totalLength); + ret.append(QString(*iter)); + ++iter; + + while (iter != end) + { + ret.append(separator + QString(*iter)); + ++iter; + } + + return ret; + } + template QString fromEnum(const T &value) requires std::is_enum_v diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 1790f4238..67371fac0 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -58,6 +58,7 @@ #include "base/utils/compare.h" #include "base/utils/fs.h" #include "base/utils/misc.h" +#include "base/utils/string.h" #include "lineedit.h" #include "torrenttagsdialog.h" @@ -363,7 +364,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged); - m_ui->tagsLineEdit->setText(QStringList(m_torrentParams.tags.cbegin(), m_torrentParams.tags.cend()).join(u", "_s)); + m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s)); connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this] { auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this); @@ -371,7 +372,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg] { m_torrentParams.tags = dlg->tags(); - m_ui->tagsLineEdit->setText(QStringList(m_torrentParams.tags.cbegin(), m_torrentParams.tags.cend()).join(u", "_s)); + m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s)); }); dlg->open(); }); diff --git a/src/gui/addtorrentparamswidget.cpp b/src/gui/addtorrentparamswidget.cpp index 5c3113f08..dfad28bff 100644 --- a/src/gui/addtorrentparamswidget.cpp +++ b/src/gui/addtorrentparamswidget.cpp @@ -33,6 +33,7 @@ #include "base/bittorrent/session.h" #include "base/bittorrent/torrent.h" #include "base/utils/compare.h" +#include "base/utils/string.h" #include "flowlayout.h" #include "fspathedit.h" #include "torrenttagsdialog.h" @@ -112,7 +113,7 @@ AddTorrentParamsWidget::AddTorrentParamsWidget(BitTorrent::AddTorrentParams addT connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg] { m_addTorrentParams.tags = dlg->tags(); - m_ui->tagsLineEdit->setText(QStringList(m_addTorrentParams.tags.cbegin(), m_addTorrentParams.tags.cend()).join(u", "_s)); + m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_addTorrentParams.tags, u", "_s)); }); dlg->open(); }); @@ -230,7 +231,7 @@ void AddTorrentParamsWidget::populate() m_addTorrentParams.stopCondition = data.value(); }); - m_ui->tagsLineEdit->setText(QStringList(m_addTorrentParams.tags.cbegin(), m_addTorrentParams.tags.cend()).join(u", "_s)); + m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_addTorrentParams.tags, u", "_s)); m_ui->startTorrentComboBox->disconnect(this); m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addPaused diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index efa4db33b..247efa2d2 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -380,10 +380,7 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons case TR_CATEGORY: return torrent->category(); case TR_TAGS: - { - const TagSet &tags = torrent->tags(); - return QStringList(tags.cbegin(), tags.cend()).join(u", "_s); - } + return Utils::String::joinIntoString(torrent->tags(), u", "_s); case TR_ADD_DATE: return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat); case TR_SEED_DATE: diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index d9dadbd1d..7f14be584 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -29,7 +29,6 @@ #include "serialize_torrent.h" #include -#include #include #include "base/bittorrent/infohash.h" @@ -37,6 +36,7 @@ #include "base/bittorrent/trackerentry.h" #include "base/path.h" #include "base/tagset.h" +#include "base/utils/string.h" namespace { @@ -106,8 +106,6 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) : (QDateTime::currentDateTime().toSecsSinceEpoch() - timeSinceActivity); }; - const TagSet &tags = torrent.tags(); - return { {KEY_TORRENT_ID, torrent.id().toString()}, {KEY_TORRENT_INFOHASHV1, torrent.infoHash().v1().toString()}, @@ -130,7 +128,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) {KEY_TORRENT_FIRST_LAST_PIECE_PRIO, torrent.hasFirstLastPiecePriority()}, {KEY_TORRENT_CATEGORY, torrent.category()}, - {KEY_TORRENT_TAGS, QStringList(tags.cbegin(), tags.cend()).join(u", "_s)}, + {KEY_TORRENT_TAGS, Utils::String::joinIntoString(torrent.tags(), u", "_s)}, {KEY_TORRENT_SUPER_SEEDING, torrent.superSeeding()}, {KEY_TORRENT_FORCE_START, torrent.isForced()}, {KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()}, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aa7647cab..330a555bf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ include_directories("../src") set(testFiles testalgorithm.cpp testbittorrenttrackerentry.cpp + testconceptsexplicitlyconvertibleto.cpp testconceptsstringable.cpp testglobal.cpp testorderedset.cpp diff --git a/test/testconceptsexplicitlyconvertibleto.cpp b/test/testconceptsexplicitlyconvertibleto.cpp new file mode 100644 index 000000000..26a0cebf1 --- /dev/null +++ b/test/testconceptsexplicitlyconvertibleto.cpp @@ -0,0 +1,54 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include + +#include +#include +#include + +#include "base/concepts/explicitlyconvertibleto.h" + +class TestExplicitlyConvertibleTo final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TestExplicitlyConvertibleTo) + +public: + TestExplicitlyConvertibleTo() = default; + +private slots: + void testExplicitlyConvertibleTo() const + { + static_assert(ExplicitlyConvertibleTo); + static_assert(!ExplicitlyConvertibleTo); + } +}; + +QTEST_APPLESS_MAIN(TestExplicitlyConvertibleTo) +#include "testconceptsexplicitlyconvertibleto.moc" diff --git a/test/testorderedset.cpp b/test/testorderedset.cpp index 5246b6355..4d3197978 100644 --- a/test/testorderedset.cpp +++ b/test/testorderedset.cpp @@ -33,6 +33,7 @@ #include "base/global.h" #include "base/orderedset.h" +#include "base/utils/string.h" class TestOrderedSet final : public QObject { @@ -57,7 +58,7 @@ private slots: OrderedSet set {u"a"_s, u"b"_s, u"c"_s}; set.intersect({u"c"_s, u"a"_s}); QCOMPARE(set.size(), 2); - QCOMPARE(set.join(u","_s), u"a,c"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,c"_s); OrderedSet emptySet; emptySet.intersect({u"a"_s}).intersect({u"c"_s});; @@ -73,24 +74,15 @@ private slots: QVERIFY(emptySet.isEmpty()); } - void testJoin() const - { - const OrderedSet set {u"a"_s, u"b"_s, u"c"_s}; - QCOMPARE(set.join(u","_s), u"a,b,c"_s); - - const OrderedSet emptySet; - QCOMPARE(emptySet.join(u","_s), u""_s); - } - void testRemove() const { OrderedSet set {u"a"_s, u"b"_s, u"c"_s}; QVERIFY(!set.remove(u"z"_s)); - QCOMPARE(set.join(u","_s), u"a,b,c"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,b,c"_s); QVERIFY(set.remove(u"b"_s)); - QCOMPARE(set.join(u","_s), u"a,c"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,c"_s); QVERIFY(set.remove(u"a"_s)); - QCOMPARE(set.join(u","_s), u"c"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"c"_s); QVERIFY(set.remove(u"c"_s)); QVERIFY(set.isEmpty()); @@ -107,15 +99,15 @@ private slots: OrderedSet set {u"a"_s, u"b"_s, u"c"_s}; set.unite(newData1); - QCOMPARE(set.join(u","_s), u"a,b,c,z"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,b,c,z"_s); set.unite(newData2); - QCOMPARE(set.join(u","_s), u"a,b,c,y,z"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,b,c,y,z"_s); set.unite(newData3); - QCOMPARE(set.join(u","_s), u"a,b,c,d,e,y,z"_s); + QCOMPARE(Utils::String::joinIntoString(set, u","_s), u"a,b,c,d,e,y,z"_s); OrderedSet emptySet; emptySet.unite(newData1).unite(newData2).unite(newData3); - QCOMPARE(emptySet.join(u","_s), u"c,d,e,y,z"_s); + QCOMPARE(Utils::String::joinIntoString(emptySet, u","_s), u"c,d,e,y,z"_s); } void testUnited() const @@ -126,11 +118,11 @@ private slots: OrderedSet set {u"a"_s, u"b"_s, u"c"_s}; - QCOMPARE(set.united(newData1).join(u","_s), u"a,b,c,z"_s); - QCOMPARE(set.united(newData2).join(u","_s), u"a,b,c,y"_s); - QCOMPARE(set.united(newData3).join(u","_s), u"a,b,c,d,e"_s); + QCOMPARE(Utils::String::joinIntoString(set.united(newData1), u","_s), u"a,b,c,z"_s); + QCOMPARE(Utils::String::joinIntoString(set.united(newData2), u","_s), u"a,b,c,y"_s); + QCOMPARE(Utils::String::joinIntoString(set.united(newData3), u","_s), u"a,b,c,d,e"_s); - QCOMPARE(OrderedSet().united(newData1).united(newData2).united(newData3).join(u","_s), u"c,d,e,y,z"_s); + QCOMPARE(Utils::String::joinIntoString(OrderedSet().united(newData1).united(newData2).united(newData3), u","_s), u"c,d,e,y,z"_s); } }; diff --git a/test/testutilsstring.cpp b/test/testutilsstring.cpp index 164b2b480..6c1c0c63c 100644 --- a/test/testutilsstring.cpp +++ b/test/testutilsstring.cpp @@ -26,12 +26,33 @@ * exception statement from your version. */ +#include #include #include #include "base/global.h" #include "base/utils/string.h" +namespace +{ + class MyString + { + public: + MyString(const QString &str) + : m_str {str} + { + } + + explicit operator QString() const + { + return m_str; + } + + private: + QString m_str; + }; +} + class TestUtilsString final : public QObject { Q_OBJECT @@ -41,6 +62,21 @@ public: TestUtilsString() = default; private slots: + void testJoinIntoString() const + { + const QList list1; + QCOMPARE(Utils::String::joinIntoString(list1, u","_s), u""_s); + + const QList list2 {u"a"_s}; + QCOMPARE(Utils::String::joinIntoString(list2, u","_s), u"a"_s); + + const QList list3 {u"a"_s, u"b"_s}; + QCOMPARE(Utils::String::joinIntoString(list3, u" , "_s), u"a , b"_s); + + const QList list4 {u"a"_s, u"b"_s, u"cd"_s}; + QCOMPARE(Utils::String::joinIntoString(list4, u"++"_s), u"a++b++cd"_s); + } + void testSplitCommand() const { QCOMPARE(Utils::String::splitCommand({}), {});