aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--lib/connection.cpp40
-rw-r--r--lib/converters.h24
-rw-r--r--lib/csapi/search.cpp191
-rw-r--r--lib/csapi/search.h203
-rw-r--r--lib/events/accountdataevents.h10
-rw-r--r--lib/events/roommessageevent.cpp23
-rw-r--r--lib/events/roommessageevent.h2
-rw-r--r--lib/room.cpp13
-rw-r--r--lib/syncdata.cpp15
-rw-r--r--lib/util.h122
11 files changed, 100 insertions, 544 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 58509eae..11d7d194 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -193,6 +193,7 @@ if (MATRIX_DOC_PATH AND GTAD_PATH)
${ABS_GTAD_PATH} --config ${CSAPI_DIR}/gtad.yaml --out ${CSAPI_DIR}
${FULL_CSAPI_SRC_DIR}
old_sync.yaml- room_initial_sync.yaml- # deprecated
+ search.yaml- # current GTAD is limited in handling move-only data
sync.yaml- # we have a better handcrafted implementation
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/lib
SOURCES ${FULL_CSAPI_DIR}/gtad.yaml
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 3d617733..998b45d1 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -277,16 +277,16 @@ void Connection::reloadCapabilities()
else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError)
qCDebug(MAIN) << "Server doesn't support /capabilities";
- if (d->capabilities.roomVersions.omitted()) {
+ if (!d->capabilities.roomVersions) {
qCWarning(MAIN) << "Pinning supported room version to 1";
- d->capabilities.roomVersions = { "1", { { "1", "stable" } } };
+ d->capabilities.roomVersions.emplace({ "1", { { "1", "stable" } } });
} else {
qCDebug(MAIN) << "Room versions:" << defaultRoomVersion()
<< "is default, full list:" << availableRoomVersions();
}
- Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ Q_ASSERT(d->capabilities.roomVersions.has_value());
emit capabilitiesLoaded();
- for (auto* r : d->roomMap)
+ for (auto* r : qAsConst(d->roomMap))
r->checkVersion();
});
}
@@ -295,7 +295,7 @@ bool Connection::loadingCapabilities() const
{
// (Ab)use the fact that room versions cannot be omitted after
// the capabilities have been loaded (see reloadCapabilities() above).
- return d->capabilities.roomVersions.omitted();
+ return !d->capabilities.roomVersions;
}
void Connection::Private::connectWithToken(const QString& userId,
@@ -363,8 +363,8 @@ void Connection::sync(int timeout)
d->syncTimeout = timeout;
Filter filter;
- filter.room->timeline->limit = 100;
- filter.room->state->lazyLoadMembers = d->lazyLoading;
+ filter.room.edit().timeline.edit().limit.emplace(100);
+ filter.room.edit().state.edit().lazyLoadMembers.emplace(d->lazyLoading);
auto job = d->syncJob =
callApi<SyncJob>(BackgroundRequest, d->data->lastEvent(), filter,
timeout);
@@ -446,7 +446,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache)
r->updateData(std::move(roomData), fromCache);
if (d->firstTimeRooms.removeOne(r)) {
emit loadedRoomState(r);
- if (!d->capabilities.roomVersions.omitted())
+ if (d->capabilities.roomVersions)
r->checkVersion();
// Otherwise, the version will be checked in reloadCapabilities()
}
@@ -1182,7 +1182,7 @@ Room* Connection::provideRoom(const QString& id, Omittable<JoinState> joinState)
// TODO: This whole function is a strong case for a RoomManager class.
Q_ASSERT_X(!id.isEmpty(), __FUNCTION__, "Empty room id");
- // If joinState.omitted(), all joinState == comparisons below are false.
+ // If joinState is empty, all joinState == comparisons below are false.
const auto roomKey = qMakePair(id, joinState == JoinState::Invite);
auto* room = d->roomMap.value(roomKey, nullptr);
if (room) {
@@ -1191,7 +1191,7 @@ Room* Connection::provideRoom(const QString& id, Omittable<JoinState> joinState)
// and emit a signal. For Invite and Join, there's no such problem.
if (room->joinState() == joinState && joinState != JoinState::Leave)
return room;
- } else if (joinState.omitted()) {
+ } else if (!joinState) {
// No Join and Leave, maybe Invite?
room = d->roomMap.value({ id, true }, nullptr);
if (room)
@@ -1200,9 +1200,7 @@ Room* Connection::provideRoom(const QString& id, Omittable<JoinState> joinState)
}
if (!room) {
- room = roomFactory()(this, id,
- joinState.omitted() ? JoinState::Join
- : joinState.value());
+ room = roomFactory()(this, id, joinState.value_or(JoinState::Join));
if (!room) {
qCCritical(MAIN) << "Failed to create a room" << id;
return nullptr;
@@ -1213,20 +1211,20 @@ Room* Connection::provideRoom(const QString& id, Omittable<JoinState> joinState)
&Connection::aboutToDeleteRoom);
emit newRoom(room);
}
- if (joinState.omitted())
+ if (!joinState)
return room;
- if (joinState == JoinState::Invite) {
+ if (*joinState == JoinState::Invite) {
// prev is either Leave or nullptr
auto* prev = d->roomMap.value({ id, false }, nullptr);
emit invitedRoom(room, prev);
} else {
- room->setJoinState(joinState.value());
+ room->setJoinState(*joinState);
// Preempt the Invite room (if any) with a room in Join/Leave state.
auto* prevInvite = d->roomMap.take({ id, true });
- if (joinState == JoinState::Join)
+ if (*joinState == JoinState::Join)
emit joinedRoom(room, prevInvite);
- else if (joinState == JoinState::Leave)
+ else if (*joinState == JoinState::Leave)
emit leftRoom(room, prevInvite);
if (prevInvite) {
const auto dcUsers = prevInvite->directChatUsers();
@@ -1431,13 +1429,13 @@ const QString Connection::SupportedRoomVersion::StableTag =
QString Connection::defaultRoomVersion() const
{
- Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ Q_ASSERT(d->capabilities.roomVersions.has_value());
return d->capabilities.roomVersions->defaultVersion;
}
QStringList Connection::stableRoomVersions() const
{
- Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ Q_ASSERT(d->capabilities.roomVersions.has_value());
QStringList l;
const auto& allVersions = d->capabilities.roomVersions->available;
for (auto it = allVersions.begin(); it != allVersions.end(); ++it)
@@ -1457,7 +1455,7 @@ inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1,
QVector<Connection::SupportedRoomVersion> Connection::availableRoomVersions() const
{
- Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ Q_ASSERT(d->capabilities.roomVersions.has_value());
QVector<SupportedRoomVersion> result;
result.reserve(d->capabilities.roomVersions->available.size());
for (auto it = d->capabilities.roomVersions->available.begin();
diff --git a/lib/converters.h b/lib/converters.h
index b753a80b..157bff27 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -206,7 +206,7 @@ template <typename T>
struct JsonConverter<Omittable<T>> {
static QJsonValue dump(const Omittable<T>& from)
{
- return from.omitted() ? QJsonValue() : toJson(from.value());
+ return from.has_value() ? toJson(from.value()) : QJsonValue();
}
static Omittable<T> load(const QJsonValue& jv)
{
@@ -378,28 +378,10 @@ namespace _impl {
static void impl(ContT& container, const QString& key,
const OmittableT& value)
{
- if (!value.omitted())
- addTo(container, key, value.value());
+ if (value)
+ addTo(container, key, *value);
}
};
-
-#if 0
- // This is a special one that unfolds optional<>
- template <typename ValT, bool Force>
- struct AddNode<optional<ValT>, Force>
- {
- template <typename ContT, typename OptionalT>
- static void impl(ContT& container,
- const QString& key, const OptionalT& value)
- {
- if (value)
- AddNode<ValT>::impl(container, key, value.value());
- else if (Force) // Edge case, no value but must put something
- AddNode<ValT>::impl(container, key, QString{});
- }
- };
-#endif
-
} // namespace _impl
static constexpr bool IfNotEmpty = false;
diff --git a/lib/csapi/search.cpp b/lib/csapi/search.cpp
deleted file mode 100644
index 9619f340..00000000
--- a/lib/csapi/search.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-/******************************************************************************
- * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
- */
-
-#include "search.h"
-
-#include "converters.h"
-
-#include <QtCore/QStringBuilder>
-
-using namespace Quotient;
-
-static const auto basePath = QStringLiteral("/_matrix/client/r0");
-
-// Converters
-namespace Quotient
-{
-
-template <>
-struct JsonObjectConverter<SearchJob::IncludeEventContext>
-{
- static void dumpTo(QJsonObject& jo,
- const SearchJob::IncludeEventContext& pod)
- {
- addParam<IfNotEmpty>(jo, QStringLiteral("before_limit"),
- pod.beforeLimit);
- addParam<IfNotEmpty>(jo, QStringLiteral("after_limit"), pod.afterLimit);
- addParam<IfNotEmpty>(jo, QStringLiteral("include_profile"),
- pod.includeProfile);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::Group>
-{
- static void dumpTo(QJsonObject& jo, const SearchJob::Group& pod)
- {
- addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::Groupings>
-{
- static void dumpTo(QJsonObject& jo, const SearchJob::Groupings& pod)
- {
- addParam<IfNotEmpty>(jo, QStringLiteral("group_by"), pod.groupBy);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::RoomEventsCriteria>
-{
- static void dumpTo(QJsonObject& jo, const SearchJob::RoomEventsCriteria& pod)
- {
- addParam<>(jo, QStringLiteral("search_term"), pod.searchTerm);
- addParam<IfNotEmpty>(jo, QStringLiteral("keys"), pod.keys);
- addParam<IfNotEmpty>(jo, QStringLiteral("filter"), pod.filter);
- addParam<IfNotEmpty>(jo, QStringLiteral("order_by"), pod.orderBy);
- addParam<IfNotEmpty>(jo, QStringLiteral("event_context"),
- pod.eventContext);
- addParam<IfNotEmpty>(jo, QStringLiteral("include_state"),
- pod.includeState);
- addParam<IfNotEmpty>(jo, QStringLiteral("groupings"), pod.groupings);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::Categories>
-{
- static void dumpTo(QJsonObject& jo, const SearchJob::Categories& pod)
- {
- addParam<IfNotEmpty>(jo, QStringLiteral("room_events"), pod.roomEvents);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::UserProfile>
-{
- static void fillFrom(const QJsonObject& jo, SearchJob::UserProfile& result)
- {
- fromJson(jo.value("displayname"_ls), result.displayname);
- fromJson(jo.value("avatar_url"_ls), result.avatarUrl);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::EventContext>
-{
- static void fillFrom(const QJsonObject& jo, SearchJob::EventContext& result)
- {
- fromJson(jo.value("start"_ls), result.begin);
- fromJson(jo.value("end"_ls), result.end);
- fromJson(jo.value("profile_info"_ls), result.profileInfo);
- fromJson(jo.value("events_before"_ls), result.eventsBefore);
- fromJson(jo.value("events_after"_ls), result.eventsAfter);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::Result>
-{
- static void fillFrom(const QJsonObject& jo, SearchJob::Result& result)
- {
- fromJson(jo.value("rank"_ls), result.rank);
- fromJson(jo.value("result"_ls), result.result);
- fromJson(jo.value("context"_ls), result.context);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::GroupValue>
-{
- static void fillFrom(const QJsonObject& jo, SearchJob::GroupValue& result)
- {
- fromJson(jo.value("next_batch"_ls), result.nextBatch);
- fromJson(jo.value("order"_ls), result.order);
- fromJson(jo.value("results"_ls), result.results);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::ResultRoomEvents>
-{
- static void fillFrom(const QJsonObject& jo,
- SearchJob::ResultRoomEvents& result)
- {
- fromJson(jo.value("count"_ls), result.count);
- fromJson(jo.value("highlights"_ls), result.highlights);
- fromJson(jo.value("results"_ls), result.results);
- fromJson(jo.value("state"_ls), result.state);
- fromJson(jo.value("groups"_ls), result.groups);
- fromJson(jo.value("next_batch"_ls), result.nextBatch);
- }
-};
-
-template <>
-struct JsonObjectConverter<SearchJob::ResultCategories>
-{
- static void fillFrom(const QJsonObject& jo,
- SearchJob::ResultCategories& result)
- {
- fromJson(jo.value("room_events"_ls), result.roomEvents);
- }
-};
-
-} // namespace Quotient
-
-class SearchJob::Private
-{
-public:
- ResultCategories searchCategories;
-};
-
-BaseJob::Query queryToSearch(const QString& nextBatch)
-{
- BaseJob::Query _q;
- addParam<IfNotEmpty>(_q, QStringLiteral("next_batch"), nextBatch);
- return _q;
-}
-
-static const auto SearchJobName = QStringLiteral("SearchJob");
-
-SearchJob::SearchJob(const Categories& searchCategories,
- const QString& nextBatch)
- : BaseJob(HttpVerb::Post, SearchJobName, basePath % "/search",
- queryToSearch(nextBatch))
- , d(new Private)
-{
- QJsonObject _data;
- addParam<>(_data, QStringLiteral("search_categories"), searchCategories);
- setRequestData(_data);
-}
-
-SearchJob::~SearchJob() = default;
-
-const SearchJob::ResultCategories& SearchJob::searchCategories() const
-{
- return d->searchCategories;
-}
-
-BaseJob::Status SearchJob::parseJson(const QJsonDocument& data)
-{
- auto json = data.object();
- if (!json.contains("search_categories"_ls))
- return { IncorrectResponse,
- "The key 'search_categories' not found in the response" };
- fromJson(json.value("search_categories"_ls), d->searchCategories);
-
- return Success;
-}
diff --git a/lib/csapi/search.h b/lib/csapi/search.h
deleted file mode 100644
index 079ac8e9..00000000
--- a/lib/csapi/search.h
+++ /dev/null
@@ -1,203 +0,0 @@
-/******************************************************************************
- * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
- */
-
-#pragma once
-
-#include "converters.h"
-
-#include "csapi/definitions/room_event_filter.h"
-
-#include "events/eventloader.h"
-#include "jobs/basejob.h"
-
-#include <QtCore/QHash>
-#include <QtCore/QVector>
-
-#include <unordered_map>
-
-namespace Quotient
-{
-
-// Operations
-
-/// Perform a server-side search.
-/*!
- * Performs a full text search across different categories.
- */
-class SearchJob : public BaseJob
-{
-public:
- // Inner data structures
-
- /// Configures whether any context for the eventsreturned are included in
- /// the response.
- struct IncludeEventContext
- {
- /// How many events before the result arereturned. By default, this is
- /// ``5``.
- Omittable<int> beforeLimit;
- /// How many events after the result arereturned. By default, this is
- /// ``5``.
- Omittable<int> afterLimit;
- /// Requests that the server returns thehistoric profile information for
- /// the usersthat sent the events that were returned.By default, this is
- /// ``false``.
- Omittable<bool> includeProfile;
- };
-
- /// Configuration for group.
- struct Group
- {
- /// Key that defines the group.
- QString key;
- };
-
- /// Requests that the server partitions the result setbased on the provided
- /// list of keys.
- struct Groupings
- {
- /// List of groups to request.
- QVector<Group> groupBy;
- };
-
- /// Mapping of category name to search criteria.
- struct RoomEventsCriteria
- {
- /// The string to search events for
- QString searchTerm;
- /// The keys to search. Defaults to all.
- QStringList keys;
- /// This takes a `filter`_.
- Omittable<RoomEventFilter> filter;
- /// The order in which to search for results.By default, this is
- /// ``"rank"``.
- QString orderBy;
- /// Configures whether any context for the eventsreturned are included
- /// in the response.
- Omittable<IncludeEventContext> eventContext;
- /// Requests the server return the current state foreach room returned.
- Omittable<bool> includeState;
- /// Requests that the server partitions the result setbased on the
- /// provided list of keys.
- Omittable<Groupings> groupings;
- };
-
- /// Describes which categories to search in and their criteria.
- struct Categories
- {
- /// Mapping of category name to search criteria.
- Omittable<RoomEventsCriteria> roomEvents;
- };
-
- /// Performs a full text search across different categories.
- struct UserProfile
- {
- /// Performs a full text search across different categories.
- QString displayname;
- /// Performs a full text search across different categories.
- QString avatarUrl;
- };
-
- /// Context for result, if requested.
- struct EventContext
- {
- /// Pagination token for the start of the chunk
- QString begin;
- /// Pagination token for the end of the chunk
- QString end;
- /// The historic profile information of theusers that sent the events
- /// returned.The ``string`` key is the user ID for whichthe profile
- /// belongs to.
- QHash<QString, UserProfile> profileInfo;
- /// Events just before the result.
- RoomEvents eventsBefore;
- /// Events just after the result.
- RoomEvents eventsAfter;
- };
-
- /// The result object.
- struct Result
- {
- /// A number that describes how closely this result matches the search.
- /// Higher is closer.
- Omittable<double> rank;
- /// The event that matched.
- RoomEventPtr result;
- /// Context for result, if requested.
- Omittable<EventContext> context;
- };
-
- /// The results for a particular group value.
- struct GroupValue
- {
- /// Token that can be used to get the next batchof results in the group,
- /// by passing as the`next_batch` parameter to the next call. Ifthis
- /// field is absent, there are no moreresults in this group.
- QString nextBatch;
- /// Key that can be used to order differentgroups.
- Omittable<int> order;
- /// Which results are in this group.
- QStringList results;
- };
-
- /// Mapping of category name to search criteria.
- struct ResultRoomEvents
- {
- /// An approximate count of the total number of results found.
- Omittable<int> count;
- /// List of words which should be highlighted, useful for stemming which
- /// may change the query terms.
- QStringList highlights;
- /// List of results in the requested order.
- std::vector<Result> results;
- /// The current state for every room in the results.This is included if
- /// the request had the``include_state`` key set with a value of
- /// ``true``.The ``string`` key is the room ID for which the
- /// ``StateEvent`` array belongs to.
- std::unordered_map<QString, StateEvents> state;
- /// Any groups that were requested.The outer ``string`` key is the group
- /// key requested (eg: ``room_id``or ``sender``). The inner ``string``
- /// key is the grouped value (eg: a room's ID or a user's ID).
- QHash<QString, QHash<QString, GroupValue>> groups;
- /// Token that can be used to get the next batch ofresults, by passing
- /// as the `next_batch` parameter tothe next call. If this field is
- /// absent, there are nomore results.
- QString nextBatch;
- };
-
- /// Describes which categories to search in and their criteria.
- struct ResultCategories
- {
- /// Mapping of category name to search criteria.
- Omittable<ResultRoomEvents> roomEvents;
- };
-
- // Construction/destruction
-
- /*! Perform a server-side search.
- * \param searchCategories
- * Describes which categories to search in and their criteria.
- * \param nextBatch
- * The point to return events from. If given, this should be a
- * ``next_batch`` result from a previous call to this endpoint.
- */
- explicit SearchJob(const Categories& searchCategories,
- const QString& nextBatch = {});
-
- ~SearchJob() override;
-
- // Result properties
-
- /// Describes which categories to search in and their criteria.
- const ResultCategories& searchCategories() const;
-
-protected:
- Status parseJson(const QJsonDocument& data) override;
-
-private:
- class Private;
- QScopedPointer<Private> d;
-};
-
-} // namespace Quotient
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
index 31176766..a55016d9 100644
--- a/lib/events/accountdataevents.h
+++ b/lib/events/accountdataevents.h
@@ -1,5 +1,3 @@
-#include <utility>
-
/******************************************************************************
* Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net>
*
@@ -34,13 +32,13 @@ struct TagRecord {
order_type order;
- TagRecord(order_type order = none) : order(order) {}
+ TagRecord(order_type order = none) : order(std::move(order)) {}
bool operator<(const TagRecord& other) const
{
- // Per The Spec, rooms with no order should be after those with order
- return !order.omitted()
- && (other.order.omitted() || order.value() < other.order.value());
+ // Per The Spec, rooms with no order should be after those with order,
+ // against optional<>::operator<() convention.
+ return order && (!other.order || *order < *other.order);
}
};
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
index 09562d65..078ae70a 100644
--- a/lib/events/roommessageevent.cpp
+++ b/lib/events/roommessageevent.cpp
@@ -95,6 +95,11 @@ MsgType jsonToMsgType(const QString& matrixType)
return MsgType::Unknown;
}
+inline bool isReplacement(const Omittable<RelatesTo>& rel)
+{
+ return rel && rel->type == RelatesTo::ReplacementTypeId();
+}
+
QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody,
const QString& jsonMsgType,
TypedBase* content)
@@ -111,6 +116,7 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody,
// After the above, we know for sure that the content is TextContent
// and that its RelatesTo structure is not omitted
auto* textContent = static_cast<const TextContent*>(content);
+ Q_ASSERT(textContent && textContent->relatesTo.has_value());
if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) {
auto newContentJson = json.take("m.new_content"_ls).toObject();
newContentJson.insert(BodyKey, plainBody);
@@ -243,9 +249,7 @@ QString RoomMessageEvent::replacedEvent() const
return {};
const auto& rel = static_cast<const TextContent*>(content())->relatesTo;
- return !rel.omitted() && rel->type == RelatesTo::ReplacementTypeId()
- ? rel->eventId
- : QString();
+ return isReplacement(rel) ? rel->eventId : QString();
}
QString rawMsgTypeForMimeType(const QMimeType& mimeType)
@@ -269,10 +273,10 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi)
return rawMsgTypeForMimeType(QMimeDatabase().mimeTypeForFile(fi));
}
-TextContent::TextContent(const QString& text, const QString& contentType,
+TextContent::TextContent(QString text, const QString& contentType,
Omittable<RelatesTo> relatesTo)
: mimeType(QMimeDatabase().mimeTypeForName(contentType))
- , body(text)
+ , body(std::move(text))
, relatesTo(std::move(relatesTo))
{
if (contentType == HtmlContentTypeId)
@@ -304,10 +308,9 @@ TextContent::TextContent(const QJsonObject& json)
static const auto PlainTextMimeType = db.mimeTypeForName("text/plain");
static const auto HtmlMimeType = db.mimeTypeForName("text/html");
- const auto actualJson =
- relatesTo.omitted() || relatesTo->type != RelatesTo::ReplacementTypeId()
- ? json
- : json.value("m.new_content"_ls).toObject();
+ const auto actualJson = isReplacement(relatesTo)
+ ? json.value("m.new_content"_ls).toObject()
+ : json;
// Special-casing the custom matrix.org's (actually, Riot's) way
// of sending HTML messages.
if (actualJson["format"_ls].toString() == HtmlContentTypeId) {
@@ -331,7 +334,7 @@ void TextContent::fillJson(QJsonObject* json) const
json->insert(FormatKey, HtmlContentTypeId);
json->insert(FormattedBodyKey, body);
}
- if (!relatesTo.omitted()) {
+ if (relatesTo) {
json->insert(QStringLiteral("m.relates_to"),
QJsonObject { { relatesTo->type, relatesTo->eventId } });
if (relatesTo->type == RelatesTo::ReplacementTypeId()) {
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
index e95aabfc..ded5e572 100644
--- a/lib/events/roommessageevent.h
+++ b/lib/events/roommessageevent.h
@@ -114,7 +114,7 @@ namespace EventContent {
*/
class TextContent : public TypedBase {
public:
- TextContent(const QString& text, const QString& contentType,
+ TextContent(QString text, const QString& contentType,
Omittable<RelatesTo> relatesTo = none);
explicit TextContent(const QJsonObject& json);
diff --git a/lib/room.cpp b/lib/room.cpp
index c4cdac04..0806e30d 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -1201,16 +1201,14 @@ QString Room::decryptMessage(QByteArray cipher, const QString& senderKey,
int Room::joinedCount() const
{
- return d->summary.joinedMemberCount.omitted()
- ? d->membersMap.size()
- : d->summary.joinedMemberCount.value();
+ return d->summary.joinedMemberCount.value_or(d->membersMap.size());
}
int Room::invitedCount() const
{
// TODO: Store invited users in Room too
- Q_ASSERT(!d->summary.invitedMemberCount.omitted());
- return d->summary.invitedMemberCount.value();
+ Q_ASSERT(d->summary.invitedMemberCount.has_value());
+ return d->summary.invitedMemberCount.value_or(0);
}
int Room::totalMemberCount() const { return joinedCount() + invitedCount(); }
@@ -2620,9 +2618,8 @@ QString Room::Private::calculateDisplayname() const
const bool emptyRoom =
membersMap.isEmpty()
|| (membersMap.size() == 1 && isLocalUser(*membersMap.begin()));
- const bool nonEmptySummary =
- !summary.heroes.omitted() && !summary.heroes->empty();
- auto shortlist = nonEmptySummary ? buildShortlist(summary.heroes.value())
+ const bool nonEmptySummary = summary.heroes && !summary.heroes->empty();
+ auto shortlist = nonEmptySummary ? buildShortlist(*summary.heroes)
: !emptyRoom ? buildShortlist(membersMap)
: users_shortlist_t {};
diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp
index 5b47b30f..89c512a2 100644
--- a/lib/syncdata.cpp
+++ b/lib/syncdata.cpp
@@ -30,8 +30,7 @@ const QString SyncRoomData::UnreadCountKey =
bool RoomSummary::isEmpty() const
{
- return joinedMemberCount.omitted() && invitedMemberCount.omitted()
- && heroes.omitted();
+ return !joinedMemberCount && !invitedMemberCount && !heroes;
}
bool RoomSummary::merge(const RoomSummary& other)
@@ -46,12 +45,12 @@ QDebug Quotient::operator<<(QDebug dbg, const RoomSummary& rs)
{
QDebugStateSaver _(dbg);
QStringList sl;
- if (!rs.joinedMemberCount.omitted())
- sl << QStringLiteral("joined: %1").arg(rs.joinedMemberCount.value());
- if (!rs.invitedMemberCount.omitted())
- sl << QStringLiteral("invited: %1").arg(rs.invitedMemberCount.value());
- if (!rs.heroes.omitted())
- sl << QStringLiteral("heroes: [%1]").arg(rs.heroes.value().join(','));
+ if (rs.joinedMemberCount)
+ sl << QStringLiteral("joined: %1").arg(*rs.joinedMemberCount);
+ if (rs.invitedMemberCount)
+ sl << QStringLiteral("invited: %1").arg(*rs.invitedMemberCount);
+ if (rs.heroes)
+ sl << QStringLiteral("heroes: [%1]").arg(rs.heroes->join(','));
dbg.nospace().noquote() << sl.join(QStringLiteral("; "));
return dbg;
}
diff --git a/lib/util.h b/lib/util.h
index f7a81b2a..15c9fec8 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -24,6 +24,7 @@
#include <functional>
#include <memory>
#include <unordered_map>
+#include <optional>
// Along the lines of Q_DISABLE_COPY - the upstream version comes in Qt 5.13
#define DISABLE_MOVE(_ClassName) \
@@ -43,68 +44,45 @@ struct HashQ {
template <typename KeyT, typename ValT>
using UnorderedMap = std::unordered_map<KeyT, ValT, HashQ<KeyT>>;
-struct NoneTag {};
-constexpr NoneTag none {};
+inline constexpr auto none = std::nullopt;
-/** A crude substitute for `optional` while we're not C++17
+/** `std::optional` with tweaks
*
- * Only works with default-constructible types.
+ * Due to tweaks, only works with default-constructible types.
*/
template <typename T>
-class Omittable {
+class Omittable : public std::optional<T> {
static_assert(!std::is_reference<T>::value,
"You cannot make an Omittable<> with a reference type");
public:
+ using base_type = std::optional<T>;
using value_type = std::decay_t<T>;
-
- explicit Omittable() : Omittable(none) {}
- Omittable(NoneTag) : _value(value_type()), _omitted(true) {}
- Omittable(const value_type& val) : _value(val) {}
- Omittable(value_type&& val) : _value(std::move(val)) {}
- Omittable<T>& operator=(const value_type& val)
- {
- _value = val;
- _omitted = false;
- return *this;
- }
- Omittable<T>& operator=(value_type&& val)
+ static_assert(std::is_default_constructible_v<value_type>,
+ "Omittable<> requires a default-constructible type");
+
+ using std::optional<T>::optional;
+
+ // Overload emplace() to allow passing braced-init-lists (the standard
+ // emplace() does direct-initialisation but not direct-list-initialisation).
+ using base_type::emplace;
+ T& emplace(const T& val) { return base_type::emplace(val); }
+ T& emplace(T&& val) { return base_type::emplace(std::move(val)); }
+
+ // use value_or() or check (with operator! or has_value) before accessing
+ // with operator-> or operator*
+ // The technical reason is that Xcode 10 has incomplete std::optional
+ // that has no value(); but using value() may also mean that you rely
+ // on the optional throwing an exception (which is not assumed practice
+ // throughout Quotient) or that you spend unnecessary CPU cycles on
+ // an extraneous has_value() check.
+ value_type& value() = delete;
+ const value_type& value() const = delete;
+ value_type& edit()
{
- // For some reason GCC complains about -Wmaybe-uninitialized
- // in the context of using Omittable<bool> with converters.h;
- // though the logic looks very much benign (GCC bug???)
- _value = std::move(val);
- _omitted = false;
- return *this;
+ return this->has_value() ? base_type::operator*() : this->emplace();
}
- bool operator==(const value_type& rhs) const
- {
- return !omitted() && value() == rhs;
- }
- friend bool operator==(const value_type& lhs,
- const Omittable<value_type>& rhs)
- {
- return rhs == lhs;
- }
- bool operator!=(const value_type& rhs) const { return !operator==(rhs); }
- friend bool operator!=(const value_type& lhs,
- const Omittable<value_type>& rhs)
- {
- return !(rhs == lhs);
- }
-
- bool omitted() const { return _omitted; }
- const value_type& value() const
- {
- Q_ASSERT(!_omitted);
- return _value;
- }
- value_type& editValue()
- {
- _omitted = false;
- return _value;
- }
/// Merge the value from another Omittable
/// \return true if \p other is not omitted and the value of
/// the current Omittable was different (or omitted);
@@ -114,26 +92,20 @@ public:
auto merge(const Omittable<T1>& other)
-> std::enable_if_t<std::is_convertible<T1, T>::value, bool>
{
- if (other.omitted() || (!_omitted && _value == other.value()))
+ if (!other || (this->has_value() && **this == *other))
return false;
- _omitted = false;
- _value = other.value();
+ *this = other;
return true;
}
- value_type&& release()
- {
- _omitted = true;
- return std::move(_value);
- }
- const value_type* operator->() const& { return &value(); }
- value_type* operator->() & { return &editValue(); }
- const value_type& operator*() const& { return value(); }
- value_type& operator*() & { return editValue(); }
+ // Hide non-const lvalue operator-> and operator* as these are
+ // a bit too surprising: value() & doesn't lazy-create an object;
+ // and it's too easy to inadvertently change the underlying value.
-private:
- T _value;
- bool _omitted = false;
+ const value_type* operator->() const& { return base_type::operator->(); }
+ value_type* operator->() && { return base_type::operator->(); }
+ const value_type& operator*() const& { return base_type::operator*(); }
+ value_type& operator*() && { return base_type::operator*(); }
};
namespace _impl {
@@ -213,19 +185,19 @@ class Range {
using size_type = typename ArrayT::size_type;
public:
- Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {}
- Range(iterator from, iterator to) : from(from), to(to) {}
+ constexpr Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {}
+ constexpr Range(iterator from, iterator to) : from(from), to(to) {}
- size_type size() const
+ constexpr size_type size() const
{
Q_ASSERT(std::distance(from, to) >= 0);
return size_type(std::distance(from, to));
}
- bool empty() const { return from == to; }
- const_iterator begin() const { return from; }
- const_iterator end() const { return to; }
- iterator begin() { return from; }
- iterator end() { return to; }
+ constexpr bool empty() const { return from == to; }
+ constexpr const_iterator begin() const { return from; }
+ constexpr const_iterator end() const { return to; }
+ constexpr iterator begin() { return from; }
+ constexpr iterator end() { return to; }
private:
iterator from;
@@ -239,8 +211,8 @@ private:
*/
template <typename InputIt, typename ForwardIt, typename Pred>
inline std::pair<InputIt, ForwardIt> findFirstOf(InputIt first, InputIt last,
- ForwardIt sFirst,
- ForwardIt sLast, Pred pred)
+ ForwardIt sFirst,
+ ForwardIt sLast, Pred pred)
{
for (; first != last; ++first)
for (auto it = sFirst; it != sLast; ++it)