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"