File indexing completed on 2024-05-12 16:25:51

0001 /*
0002 
0003  * SPDX-FileCopyrightText: 2016 Riccardo Iaconelli <riccardo@kde.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  *
0007  */
0008 
0009 #include "roommodel.h"
0010 #include "rocketchataccount.h"
0011 #include "ruqola_rooms_debug.h"
0012 #include "usersforroommodel.h"
0013 #include <KLocalizedString>
0014 
0015 #include <QDir>
0016 #include <QFile>
0017 #include <QIcon>
0018 #include <QJsonArray>
0019 
0020 RoomModel::RoomModel(RocketChatAccount *account, QObject *parent)
0021     : QAbstractListModel(parent)
0022     , mRocketChatAccount(account)
0023 {
0024     connect(account, &RocketChatAccount::ownUserPreferencesChanged, this, [this] {
0025         Q_EMIT dataChanged(index(0), index(rowCount() - 1), {RoomRoles::RoomSection});
0026     });
0027 }
0028 
0029 RoomModel::~RoomModel()
0030 {
0031     // VERIFY qDeleteAll(mRoomsList);
0032 }
0033 
0034 void RoomModel::clear()
0035 {
0036     if (!mRoomsList.isEmpty()) {
0037         beginResetModel();
0038         mRoomsList.clear();
0039         qDeleteAll(mRoomsList);
0040         endResetModel();
0041     }
0042 }
0043 
0044 QVector<Room *> RoomModel::findRoomNameConstains(const QString &str) const
0045 {
0046     QVector<Room *> rooms;
0047     for (Room *r : std::as_const(mRoomsList)) {
0048         if (r->displayRoomName().contains(str)) {
0049             rooms.append(r);
0050         }
0051     }
0052     return rooms;
0053 }
0054 
0055 Room *RoomModel::findRoom(const QString &roomID) const
0056 {
0057     for (Room *r : std::as_const(mRoomsList)) {
0058         if (r->roomId() == roomID) {
0059             return r;
0060         }
0061     }
0062     return nullptr;
0063 }
0064 
0065 // Clear data and refill it with data in the cache, if there is
0066 void RoomModel::reset()
0067 {
0068     clear();
0069 }
0070 
0071 int RoomModel::rowCount(const QModelIndex &parent) const
0072 {
0073     Q_UNUSED(parent)
0074     return mRoomsList.size();
0075 }
0076 
0077 QVariant RoomModel::data(const QModelIndex &index, int role) const
0078 {
0079     if (index.row() < 0 || index.row() >= mRoomsList.count()) {
0080         return {};
0081     }
0082 
0083     Room *r = mRoomsList.at(index.row());
0084 
0085     if (role == Qt::DisplayRole) {
0086         if (!r->parentRid().isEmpty()) {
0087             return r->fName();
0088         } else {
0089             if (mRocketChatAccount) {
0090                 return mRocketChatAccount->useRealName() ? r->displayFName() : r->name();
0091             } else {
0092                 return r->name();
0093             }
0094         }
0095     }
0096 
0097     switch (role) {
0098     case RoomModel::RoomName:
0099         return r->name();
0100     case RoomModel::RoomFName:
0101         return r->displayFName();
0102     case RoomModel::RoomId:
0103         return r->roomId();
0104     case RoomModel::RoomSelected:
0105         return r->selected();
0106     case RoomModel::RoomType:
0107         return QVariant::fromValue(r->channelType());
0108     case RoomModel::RoomOwnerUserId:
0109         return r->roomCreatorUserId();
0110     case RoomModel::RoomOwnerUserName:
0111         return r->roomOwnerUserName();
0112     case RoomModel::RoomTopic:
0113         return r->topic();
0114     case RoomModel::RoomMutedUsers:
0115         return r->mutedUsers();
0116     case RoomModel::RoomJitsiTimeout:
0117         return r->jitsiTimeout();
0118     case RoomModel::RoomReadOnly:
0119         return r->readOnly();
0120     case RoomModel::RoomAnnouncement:
0121         return r->announcement();
0122     case RoomModel::RoomUnread:
0123         return r->unread();
0124     case RoomModel::RoomOpen:
0125         return r->open();
0126     case RoomModel::RoomAlert:
0127         return r->alert();
0128     case RoomModel::RoomFavorite:
0129         return r->favorite();
0130     case RoomModel::RoomSection:
0131         return QVariant::fromValue(section(r));
0132     case RoomModel::RoomIcon:
0133     case Qt::DecorationRole:
0134         return icon(r);
0135     case RoomModel::RoomOtr:
0136         // TODO implement it.
0137         return {};
0138     case RoomModel::RoomUserMentions:
0139         return r->userMentions();
0140     case RoomModel::RoomIgnoredUsers:
0141         return r->ignoredUsers();
0142     case RoomModel::RoomAutotranslateLanguage:
0143         return r->autoTranslateLanguage();
0144     case RoomModel::RoomDirectChannelUserId:
0145         return r->directChannelUserId();
0146     case RoomModel::RoomAvatarInfo:
0147         return QVariant::fromValue(r->avatarInfo());
0148     case RoomModel::RoomTeamId:
0149         return r->teamInfo().teamId();
0150     case RoomModel::RoomTeamIsMain:
0151         return r->teamInfo().mainTeam();
0152     case RoomModel::RoomLastMessageAt:
0153         return r->lastMessageAt();
0154     case RoomModel::UserOffline:
0155         return userOffline(r);
0156     case RoomModel::HideBadgeForMention:
0157         return r->hideBadgeForMention();
0158     case Qt::ToolTipRole:
0159         return generateToolTip(r);
0160     }
0161     return {};
0162 }
0163 
0164 void RoomModel::addRoom(const QString &roomID, const QString &roomName, bool selected)
0165 {
0166     if (roomID.isEmpty() || roomName.isEmpty()) {
0167         qCDebug(RUQOLA_ROOMS_LOG) << " Impossible to add a room";
0168         return;
0169     }
0170     qCDebug(RUQOLA_ROOMS_LOG) << "Adding room : roomId: " << roomID << " room Name " << roomName << " isSelected : " << selected;
0171 
0172     Room *r = createNewRoom();
0173     r->setRoomId(roomID);
0174     r->setName(roomName);
0175     r->setSelected(selected);
0176     if (!addRoom(r)) {
0177         qCWarning(RUQOLA_ROOMS_LOG) << "Failed to add room";
0178     }
0179 }
0180 
0181 Room *RoomModel::createNewRoom()
0182 {
0183     auto r = new Room(mRocketChatAccount, this);
0184     connect(r, &Room::alertChanged, this, &RoomModel::needToUpdateNotification);
0185     connect(r, &Room::unreadChanged, this, &RoomModel::needToUpdateNotification);
0186     connect(r, &Room::openChanged, this, &RoomModel::needToUpdateNotification);
0187     connect(r, &Room::needAttention, this, &RoomModel::roomNeedAttention);
0188     return r;
0189 }
0190 
0191 void RoomModel::getUnreadAlertFromAccount(bool &hasAlert, int &nbUnread) const
0192 {
0193     for (int i = 0, total = mRoomsList.count(); i < total; ++i) {
0194         Room *room = mRoomsList.at(i);
0195         if (room->open()) {
0196             if (room->alert()) {
0197                 hasAlert = true;
0198             }
0199             nbUnread += room->unread();
0200         }
0201     }
0202 }
0203 
0204 void RoomModel::updateSubscriptionRoom(const QJsonObject &roomData)
0205 {
0206     // Use "_id"
0207     QString rId = roomData.value(QLatin1String("rid")).toString();
0208     if (rId.isEmpty()) {
0209         rId = roomData.value(QLatin1String("_id")).toString();
0210     }
0211     if (!rId.isEmpty()) {
0212         const int roomCount = mRoomsList.size();
0213         for (int i = 0; i < roomCount; ++i) {
0214             Room *room = mRoomsList.at(i);
0215             if (room->roomId() == rId) {
0216                 qCDebug(RUQOLA_ROOMS_LOG) << " void RoomModel::updateSubscriptionRoom(const QJsonArray &array) room found";
0217                 room->updateSubscriptionRoom(roomData);
0218                 Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
0219 
0220                 break;
0221             }
0222         }
0223     } else {
0224         qCWarning(RUQOLA_ROOMS_LOG) << "RoomModel::updateSubscriptionRoom incorrect jsonobject " << roomData;
0225     }
0226 }
0227 
0228 QString RoomModel::insertRoom(const QJsonObject &room)
0229 {
0230     Room *r = createNewRoom();
0231     r->parseInsertRoom(room);
0232     qCDebug(RUQOLA_ROOMS_LOG) << "Inserting room" << r->name() << r->roomId() << r->topic();
0233     if (addRoom(r)) {
0234         return r->roomId();
0235     }
0236     return {};
0237 }
0238 
0239 void RoomModel::addRoom(const QJsonObject &room)
0240 {
0241     Room *r = createNewRoom();
0242     r->parseSubscriptionRoom(room);
0243     qCDebug(RUQOLA_ROOMS_LOG) << "Adding room subscription" << r->name() << r->roomId() << r->topic();
0244     if (!addRoom(r)) {
0245         qCDebug(RUQOLA_ROOMS_LOG) << "Impossible to add room: " << r->name();
0246     }
0247 }
0248 
0249 Room::TeamRoomInfo RoomModel::roomFromTeamId(const QString &teamId)
0250 {
0251     for (int row = 0; row < rowCount(); ++row) {
0252         const QModelIndex modelIndex = index(row, 0);
0253         if (modelIndex.data(RoomModel::RoomTeamIsMain).toBool()) {
0254             if (modelIndex.data(RoomModel::RoomTeamId).toString() == teamId) {
0255                 Room::TeamRoomInfo teamInfo;
0256                 teamInfo.teamName = modelIndex.data(RoomModel::RoomName).toString();
0257                 teamInfo.teamIdentifier = modelIndex.data(RoomModel::RoomId).toString();
0258                 return teamInfo;
0259             }
0260         }
0261     }
0262     return {};
0263 }
0264 
0265 bool RoomModel::addRoom(Room *room)
0266 {
0267     qCDebug(RUQOLA_ROOMS_LOG) << " void RoomModel::addRoom(const Room &room)" << room->name();
0268     const int roomCount = mRoomsList.count();
0269     for (int i = 0; i < roomCount; ++i) {
0270         if (mRoomsList.at(i)->roomId() == room->roomId()) {
0271             qCDebug(RUQOLA_ROOMS_LOG) << " room already exist " << room->roomId() << " A bug ? ";
0272             delete room;
0273             return false;
0274         }
0275     }
0276     beginInsertRows(QModelIndex(), roomCount, roomCount);
0277     qCDebug(RUQOLA_ROOMS_LOG) << "Inserting room at position" << roomCount << " room name " << room->name();
0278     mRoomsList.append(room);
0279     endInsertRows();
0280     return true;
0281 }
0282 
0283 void RoomModel::removeRoom(const QString &roomId)
0284 {
0285     const int roomCount = mRoomsList.count();
0286     for (int i = 0; i < roomCount; ++i) {
0287         if (mRoomsList.at(i)->roomId() == roomId) {
0288             Q_EMIT roomRemoved(roomId);
0289             beginRemoveRows(QModelIndex(), i, i);
0290             mRoomsList.takeAt(i)->deleteLater();
0291             endRemoveRows();
0292             break;
0293         }
0294     }
0295 }
0296 
0297 void RoomModel::updateSubscription(const QJsonArray &array)
0298 {
0299     const QString actionName = array[0].toString();
0300     const QJsonObject roomData = array[1].toObject();
0301     if (actionName == QLatin1String("removed")) {
0302         qCDebug(RUQOLA_ROOMS_LOG) << "REMOVE ROOM name "
0303                                   << " rid " << roomData.value(QLatin1String("rid"));
0304         const QString id = roomData.value(QLatin1String("rid")).toString();
0305         removeRoom(id);
0306     } else if (actionName == QLatin1String("inserted")) {
0307         qCDebug(RUQOLA_ROOMS_LOG) << "INSERT ROOM  name " << roomData.value(QLatin1String("name")) << " rid " << roomData.value(QLatin1String("rid"));
0308         // TODO fix me!
0309         addRoom(roomData);
0310 
0311         // addRoom(roomData.value(QLatin1String("rid")).toString(), roomData.value(QLatin1String("name")).toString(), false);
0312     } else if (actionName == QLatin1String("updated")) {
0313         qCDebug(RUQOLA_ROOMS_LOG) << "UPDATE ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid"))
0314                                   << " roomData " << roomData;
0315         updateSubscriptionRoom(roomData);
0316     } else if (actionName == QLatin1String("changed")) {
0317         // qDebug() << "CHANGED ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid")) << " roomData
0318         // " << roomData;
0319         qCDebug(RUQOLA_ROOMS_LOG) << "CHANGED ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid"))
0320                                   << " roomData " << roomData;
0321         qCDebug(RUQOLA_ROOMS_LOG) << "RoomModel::updateSubscription Not implementer changed room yet" << array;
0322         updateRoom(roomData);
0323     } else {
0324         qCDebug(RUQOLA_ROOMS_LOG) << "RoomModel::updateSubscription Undefined type" << actionName;
0325     }
0326 }
0327 
0328 void RoomModel::updateRoom(const QJsonObject &roomData)
0329 {
0330     qCDebug(RUQOLA_ROOMS_LOG) << " void RoomModel::updateRoom(const QJsonObject &roomData)" << roomData;
0331     // TODO fix me!
0332     // Use "_id"
0333     QString rId = roomData.value(QLatin1String("rid")).toString();
0334     if (rId.isEmpty()) {
0335         rId = roomData.value(QLatin1String("_id")).toString();
0336     }
0337     if (!rId.isEmpty()) {
0338         const int roomCount = mRoomsList.size();
0339         for (int i = 0; i < roomCount; ++i) {
0340             Room *room = mRoomsList.at(i);
0341             if (room->roomId() == rId) {
0342                 qCDebug(RUQOLA_ROOMS_LOG) << " void RoomModel::updateRoom(const QJsonArray &array) room found";
0343                 room->parseUpdateRoom(roomData);
0344                 Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
0345 
0346                 break;
0347             }
0348         }
0349     } else {
0350         qCWarning(RUQOLA_ROOMS_LOG) << "RoomModel::updateRoom incorrect jsonobject " << roomData;
0351         // qWarning() << "RoomModel::updateRoom incorrect jsonobject "<< roomData;
0352     }
0353 }
0354 
0355 void RoomModel::userStatusChanged(const User &user)
0356 {
0357     const int roomCount = mRoomsList.count();
0358     for (int i = 0; i < roomCount; ++i) {
0359         Room *room = mRoomsList.at(i);
0360         if (room->name() == user.userName()) {
0361             const QModelIndex idx = createIndex(i, 0);
0362             Q_EMIT dataChanged(idx, idx);
0363         }
0364         room->usersModelForRoom()->setUserStatusChanged(user);
0365     }
0366 }
0367 
0368 UsersForRoomModel *RoomModel::usersModelForRoom(const QString &roomId) const
0369 {
0370     const int roomCount = mRoomsList.count();
0371     for (int i = 0; i < roomCount; ++i) {
0372         Room *room = mRoomsList.at(i);
0373         if (room->roomId() == roomId) {
0374             return room->usersModelForRoom();
0375         }
0376     }
0377     qCWarning(RUQOLA_ROOMS_LOG) << " Users model for room undefined !";
0378     return nullptr;
0379 }
0380 
0381 MessagesModel *RoomModel::messageModel(const QString &roomId) const
0382 {
0383     const int roomCount = mRoomsList.count();
0384     for (int i = 0; i < roomCount; ++i) {
0385         Room *room = mRoomsList.at(i);
0386         if (room->roomId() == roomId) {
0387             return room->messageModel();
0388         }
0389     }
0390     return {};
0391 }
0392 
0393 QString RoomModel::inputMessage(const QString &roomId) const
0394 {
0395     const int roomCount = mRoomsList.count();
0396     for (int i = 0; i < roomCount; ++i) {
0397         Room *room = mRoomsList.at(i);
0398         if (room->roomId() == roomId) {
0399             return room->inputMessage();
0400         }
0401     }
0402     return {};
0403 }
0404 
0405 void RoomModel::setInputMessage(const QString &roomId, const QString &inputMessage)
0406 {
0407     const int roomCount = mRoomsList.count();
0408     for (int i = 0; i < roomCount; ++i) {
0409         Room *room = mRoomsList.at(i);
0410         if (room->roomId() == roomId) {
0411             room->setInputMessage(inputMessage);
0412             return;
0413         }
0414     }
0415 }
0416 
0417 RoomModel::Section RoomModel::section(Room *r) const
0418 {
0419     const Room::RoomType roomType = r->channelType();
0420     if (mRocketChatAccount && mRocketChatAccount->sortUnreadOnTop() && (r->unread() > 0 || r->alert())) {
0421         if (!r->hideUnreadStatus()) {
0422             return Section::Unread;
0423         }
0424     }
0425     if (r->favorite() && mRocketChatAccount && mRocketChatAccount->sortFavoriteChannels()) {
0426         return Section::Favorites;
0427     } else if (r->teamInfo().mainTeam()) {
0428         return Section::Teams;
0429     }
0430     switch (roomType) {
0431     case Room::RoomType::Private: {
0432         if (r->parentRid().isEmpty()) {
0433             return Section::Rooms;
0434         } else {
0435             return Section::Discussions;
0436         }
0437     }
0438     case Room::RoomType::Channel: {
0439         if (r->parentRid().isEmpty()) {
0440             return Section::Rooms;
0441         } else {
0442             return Section::Discussions;
0443         }
0444     }
0445     case Room::RoomType::Direct: {
0446         return Section::PrivateMessages;
0447     }
0448     case Room::RoomType::Unknown:
0449         break;
0450     }
0451     return Section::Unknown;
0452 }
0453 
0454 bool RoomModel::userOffline(Room *r) const
0455 {
0456     if (r->channelType() == Room::RoomType::Direct) {
0457         return mRocketChatAccount ? mRocketChatAccount->userIsOffline(r->name()) : false;
0458     }
0459     return false;
0460 }
0461 
0462 QString RoomModel::generateToolTip(Room *r) const
0463 {
0464     if (r->teamInfo().mainTeam()) {
0465         return i18n("Teams Room");
0466     }
0467 
0468     switch (r->channelType()) {
0469     case Room::RoomType::Private:
0470         if (r->parentRid().isEmpty()) {
0471             return i18n("Private Room");
0472         } else {
0473             return i18n("Discussion Room");
0474         }
0475         break;
0476     case Room::RoomType::Channel:
0477         return i18n("Channel Room");
0478     case Room::RoomType::Direct: {
0479         return mRocketChatAccount ? mRocketChatAccount->userStatusStr(r->name()) : QString();
0480     }
0481     case Room::RoomType::Unknown:
0482         break;
0483     }
0484     return {};
0485 }
0486 
0487 QIcon RoomModel::icon(Room *r) const
0488 {
0489     if (r->teamInfo().mainTeam()) {
0490         return QIcon::fromTheme(QStringLiteral("group"));
0491     }
0492 
0493     // TODO add team icon support.
0494     switch (r->channelType()) {
0495     case Room::RoomType::Private:
0496         if (r->parentRid().isEmpty()) {
0497             return QIcon::fromTheme(QStringLiteral("lock"));
0498         } else {
0499             // TODO use a specific icon for discussion
0500         }
0501         break;
0502     case Room::RoomType::Channel:
0503         if (r->unread() > 0 || r->alert()) {
0504             return QIcon::fromTheme(QStringLiteral("irc-channel-active"));
0505         } else {
0506             return QIcon::fromTheme(QStringLiteral("irc-channel-inactive"));
0507         }
0508     case Room::RoomType::Direct: {
0509         const QString userStatusIconFileName = mRocketChatAccount ? mRocketChatAccount->userStatusIconFileName(r->name()) : QString();
0510         if (userStatusIconFileName.isEmpty()) {
0511             return QIcon::fromTheme(QStringLiteral("user-available"));
0512         } else {
0513             return QIcon::fromTheme(userStatusIconFileName);
0514         }
0515     }
0516     case Room::RoomType::Unknown:
0517         break;
0518     }
0519     return {};
0520 }
0521 
0522 QModelIndex RoomModel::indexForRoomName(const QString &roomName) const
0523 {
0524     for (int row = 0; row < rowCount(); ++row) {
0525         const QModelIndex modelIndex = index(row, 0);
0526         if (modelIndex.data(RoomModel::RoomName) == roomName) {
0527             return modelIndex;
0528         }
0529     }
0530     return {};
0531 }
0532 
0533 QString RoomModel::sectionName(Section sectionId)
0534 {
0535     switch (sectionId) {
0536     case RoomModel::Section::Unread:
0537         return i18n("Unread");
0538     case RoomModel::Section::Favorites:
0539         return i18n("Favorites");
0540     case RoomModel::Section::Teams:
0541         return i18n("Teams");
0542     case RoomModel::Section::Rooms:
0543         return i18n("Rooms");
0544     case RoomModel::Section::Discussions:
0545         return i18n("Discussions");
0546     case RoomModel::Section::PrivateMessages:
0547         return i18n("Private Messages");
0548     case RoomModel::Section::Unknown:
0549         return i18n("Unknown");
0550     case RoomModel::Section::NSections:
0551         break;
0552     }
0553     return QStringLiteral("ERROR");
0554 }
0555 
0556 #include "moc_roommodel.cpp"