File indexing completed on 2024-12-08 07:33:44

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