File indexing completed on 2024-05-12 16:25:05
0001 // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> 0002 // SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org> 0003 // SPDX-License-Identifier: GPL-2.0-or-later 0004 0005 #include "livelocationsmodel.h" 0006 0007 #include <Quotient/events/roommessageevent.h> 0008 0009 #include <QDebug> 0010 0011 using namespace Quotient; 0012 0013 bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs) 0014 { 0015 return lhs.eventId < rhs.eventId; 0016 } 0017 0018 LiveLocationsModel::LiveLocationsModel(QObject *parent) 0019 : QAbstractListModel(parent) 0020 { 0021 connect( 0022 this, 0023 &LiveLocationsModel::roomChanged, 0024 this, 0025 [this]() { 0026 for (const auto &event : m_room->messageEvents()) { 0027 addEvent(event.get()); 0028 } 0029 connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [this](const auto &events) { 0030 for (const auto &event : events) { 0031 addEvent(event.get()); 0032 } 0033 }); 0034 connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [this](const auto &events) { 0035 for (const auto &event : events) { 0036 addEvent(event.get()); 0037 } 0038 }); 0039 }, 0040 Qt::QueuedConnection); // deferred so we are sure the eventId filter is set 0041 0042 connect(this, &LiveLocationsModel::dataChanged, this, &LiveLocationsModel::boundingBoxChanged); 0043 connect(this, &LiveLocationsModel::rowsInserted, this, &LiveLocationsModel::boundingBoxChanged); 0044 } 0045 0046 int LiveLocationsModel::rowCount(const QModelIndex &parent) const 0047 { 0048 if (parent.isValid()) { 0049 return 0; 0050 } 0051 return m_locations.size(); 0052 } 0053 0054 QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const 0055 { 0056 if (!checkIndex(index)) { 0057 return {}; 0058 } 0059 0060 const auto &data = m_locations.at(index.row()); 0061 switch (roleName) { 0062 case LatitudeRole: { 0063 const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString(); 0064 if (geoUri.isEmpty()) { 0065 return {}; 0066 } 0067 const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0]; 0068 return latitude.toFloat(); 0069 } 0070 case LongitudeRole: { 0071 const auto geoUri = data.beacon["org.matrix.msc3488.location"_ls].toObject()["uri"_ls].toString(); 0072 if (geoUri.isEmpty()) { 0073 return {}; 0074 } 0075 const auto longitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1]; 0076 return longitude.toFloat(); 0077 } 0078 case AssetRole: 0079 return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"].toString(); 0080 case AuthorRole: 0081 return m_room->getUser(data.senderId); 0082 case IsLiveRole: { 0083 if (!data.beaconInfo["live"_ls].toBool()) { 0084 return false; 0085 } 0086 // TODO Qt6: port to toInteger(), timestamps are in ms since epoch, ie. 64 bit values 0087 const auto lastTs = std::max(data.beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble(), data.beacon.value("org.matrix.msc3488.ts"_ls).toDouble()); 0088 const auto timeout = data.beaconInfo.value("timeout"_ls).toDouble(600000); 0089 return lastTs + timeout >= QDateTime::currentDateTime().toMSecsSinceEpoch(); 0090 } 0091 } 0092 0093 return {}; 0094 } 0095 0096 QHash<int, QByteArray> LiveLocationsModel::roleNames() const 0097 { 0098 auto r = QAbstractListModel::roleNames(); 0099 r.insert(LatitudeRole, "latitude"); 0100 r.insert(LongitudeRole, "longitude"); 0101 r.insert(AssetRole, "asset"); 0102 r.insert(AuthorRole, "author"); 0103 r.insert(IsLiveRole, "isLive"); 0104 return r; 0105 } 0106 0107 QRectF LiveLocationsModel::boundingBox() const 0108 { 0109 QRectF bbox(QPointF(180.0, 90.0), QPointF(-180.0, -90.0)); 0110 for (auto i = 0; i < rowCount(); ++i) { 0111 const auto lat = data(index(i, 0), LatitudeRole).toDouble(); 0112 const auto lon = data(index(i, 0), LongitudeRole).toDouble(); 0113 0114 bbox.setLeft(std::min(bbox.left(), lon)); 0115 bbox.setRight(std::max(bbox.right(), lon)); 0116 bbox.setTop(std::min(bbox.top(), lat)); 0117 bbox.setBottom(std::max(bbox.bottom(), lat)); 0118 } 0119 return bbox; 0120 } 0121 0122 void LiveLocationsModel::addEvent(const Quotient::RoomEvent *event) 0123 { 0124 if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info") { 0125 LiveLocationData data; 0126 data.senderId = event->senderId(); 0127 data.beaconInfo = event->contentJson(); 0128 if (event->contentJson()["live"_ls].toBool()) { 0129 data.eventId = event->id(); 0130 } else { 0131 data.eventId = event->fullJson()["replaces_state"_ls].toString(); 0132 } 0133 updateLocationData(std::move(data)); 0134 } 0135 if (event->matrixType() == "org.matrix.msc3672.beacon"_ls) { 0136 LiveLocationData data; 0137 data.eventId = event->contentJson()["m.relates_to"_ls].toObject()["event_id"_ls].toString(); 0138 data.senderId = event->senderId(); 0139 data.beacon = event->contentJson(); 0140 updateLocationData(std::move(data)); 0141 } 0142 } 0143 0144 void LiveLocationsModel::updateLocationData(LiveLocationData &&data) 0145 { 0146 if (!m_eventId.isEmpty() && data.eventId != m_eventId) { 0147 return; 0148 } 0149 0150 auto it = std::lower_bound(m_locations.begin(), m_locations.end(), data); 0151 if (it == m_locations.end() || it->eventId != data.eventId) { 0152 const auto row = std::distance(m_locations.begin(), it); 0153 beginInsertRows({}, row, row); 0154 m_locations.insert(it, std::move(data)); 0155 endInsertRows(); 0156 return; 0157 } 0158 0159 const auto idx = index(std::distance(m_locations.begin(), it), 0); 0160 0161 // TODO Qt6: port to toInteger(), timestamps are in ms since epoch, ie. 64 bit values 0162 if (it->beacon.isEmpty() || it->beacon.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beacon.value("org.matrix.msc3488.ts"_ls).toDouble()) { 0163 it->beacon = std::move(data.beacon); 0164 } 0165 if (it->beaconInfo.isEmpty() 0166 || it->beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble() < data.beaconInfo.value("org.matrix.msc3488.ts"_ls).toDouble()) { 0167 it->beaconInfo = std::move(data.beaconInfo); 0168 } 0169 0170 Q_EMIT dataChanged(idx, idx); 0171 } 0172 0173 #include "moc_livelocationsmodel.cpp"