diff --git a/src/messagemodel.cpp b/src/messagemodel.cpp index 5da90d83..3ae37aac 100644 --- a/src/messagemodel.cpp +++ b/src/messagemodel.cpp @@ -1,188 +1,188 @@ #include #include #include #include #include // #include #include #include #include "messagemodel.h" #include "userdata.h" Message MessageModel::fromJSon(const QJsonObject& o) { Message message; message.username = o["username"].toString(); message.message = o["message"].toString(); message.userID = o["userID"].toString(); message.timestamp = (qint64) o["timestamp"].toDouble(); message.systemMessage = o["systemMessage"].toBool(); message.systemMessageType = o["type"].toString(); message.roomID = o["roomID"].toString(); message.messageID = o["messageID"].toString(); return message; } QByteArray MessageModel::serialize(const Message& message) { QJsonDocument d; QJsonObject o; o["username"] = message.username; o["message"] = message.message; o["userID"] = message.userID; o["timestamp"] = message.timestamp; o["systemMessage"] = message.systemMessage; o["type"] = message.systemMessageType; o["roomID"] = message.roomID; o["messageID"] = message.messageID; d.setObject(o); return d.toBinaryData(); } MessageModel::MessageModel(const QString &roomID, QObject* parent) : QAbstractListModel(parent), m_roomID(roomID) { - qDebug() << "Creating message Model"; +// qDebug() << "Creating message Model"; QDir cacheDir(UserData::self()->cacheBasePath()+"/rooms_cache"); // load cache if (QFile::exists(cacheDir.absoluteFilePath(roomID)) && !roomID.isEmpty()) { QFile f(cacheDir.absoluteFilePath(roomID)); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char * byteArray; quint32 length; in.readBytes(byteArray, length); QByteArray arr = QByteArray::fromRawData(byteArray, length); Message m = MessageModel::fromJSon(QJsonDocument::fromBinaryData(arr).object()); addMessage(m); // m_allMessages[m.timestamp] = m; // qDebug() << m.message; } } } } MessageModel::~MessageModel() { QDir cacheDir(UserData::self()->cacheBasePath()+"/rooms_cache"); qDebug() << "Caching to..." << cacheDir.path(); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath(m_roomID)); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); foreach (const Message m, m_allMessages) { QByteArray ms = MessageModel::serialize(m); out.writeBytes(ms, ms.size()); } } } QHash MessageModel::roleNames() const { QHash roles; roles[MessageText] = "messageText"; roles[Username] = "username"; roles[Timestamp] = "timestamp"; roles[UserID] = "userID"; roles[SystemMessage] = "systemMessage"; roles[SystemMessageType] = "type"; return roles; } qint64 MessageModel::lastTimestamp() const { if (m_allMessages.size()) { qDebug() << "returning timestamp" << m_allMessages.last().timestamp; return m_allMessages.last().timestamp; } else { return 0; } } int MessageModel::rowCount(const QModelIndex& parent) const { // qDebug() << "C++ asked for rowcount " << m_allMessages.size(); // if (m_allMessages.contains(m_currentRoom)) { return m_allMessages.size(); (void)parent; } void MessageModel::addMessage(const Message& message) { // Don't add empty messages? if (message.message.isEmpty()) { return; } auto existingMessage = qFind(m_allMessages.begin(), m_allMessages.end(), message); bool present = (existingMessage != m_allMessages.end()); auto i = qUpperBound(m_allMessages.begin(), m_allMessages.end(), message); int pos = i-m_allMessages.begin(); bool messageChanged = false; // if (qFind(m_allMessages.begin(), m_allMessages.end(), message) != m_allMessages.end()) { if (present){ // if (pos != m_allMessages.size()) { // we're at the end // qDebug() << "detecting a message change"; messageChanged = true; //Figure out a better way to update just the really changed message } else { beginInsertRows(QModelIndex(), pos, pos); } if (messageChanged) { m_allMessages.replace(pos-1, message); } else { m_allMessages.insert(i, message); } if (messageChanged) { emit dataChanged(createIndex(1, 1), createIndex(pos, 1)); } else { endInsertRows(); } } QVariant MessageModel::data(const QModelIndex& index, int role) const { int idx = index.row();//-1; if (role == MessageModel::Username) { // qDebug() << "C++ returning username" << // m_allMessages[m_currentRoom].values().at(idx).username(); return m_allMessages.at(idx).username; } else if (role == MessageModel::MessageText) { return m_allMessages.at(idx).message; } else if (role == MessageModel::Timestamp) { return QVariant(m_allMessages.at(idx).timestamp); } else if (role == MessageModel::UserID) { return QVariant(m_allMessages.at(idx).userID); } else if (role == MessageModel::SystemMessage) { // qDebug() << "System message?" << m_allMessages.at(idx).systemMessage; return QVariant(m_allMessages.at(idx).systemMessage); } else if (role == MessageModel::SystemMessageType) { return QVariant(m_allMessages.at(idx).systemMessageType); } else { return QVariant(""); } } // #include "messagelist.moc" diff --git a/src/roommodel.cpp b/src/roommodel.cpp index 22979973..e852778e 100644 --- a/src/roommodel.cpp +++ b/src/roommodel.cpp @@ -1,208 +1,212 @@ /* * * Copyright 2016 Riccardo Iaconelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "roommodel.h" #include #include #include "userdata.h" Room RoomModel::fromJSon(const QJsonObject& o) { Room r; r.name = o["name"].toString(); r.id = o["id"].toString(); + r.unread = o["unread"].toInt(0); + return r; } QByteArray RoomModel::serialize(const Room& r) { QJsonDocument d; QJsonObject o; o["name"] = r.name; o["id"] = r.id; + o["unread"] = r.unread; d.setObject(o); return d.toBinaryData(); } RoomModel::RoomModel(QObject* parent) : QAbstractListModel(parent) { // connect(UserData::self(), &UserData::loginStatusChanged, this, &RoomModel::onLoginStatusChanged); } RoomModel::~RoomModel() { QDir cacheDir(UserData::self()->cacheBasePath()); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath("rooms")); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); foreach (const Room m, m_roomsList) { QByteArray ms = RoomModel::serialize(m); out.writeBytes(ms, ms.size()); } } } void RoomModel::clear() { if (m_roomsList.size()) { beginRemoveRows(QModelIndex(), 0, rowCount()-1); m_roomsList.clear(); QAbstractItemModel::endRemoveRows(); } } // Clear data and refill it it with data in the cache, if there is void RoomModel::reset() { if (UserData::self()->cacheBasePath().isEmpty()) { return; } clear(); QDir cacheDir(UserData::self()->cacheBasePath()); // load cache if (cacheDir.exists(cacheDir.path())) { QFile f(cacheDir.absoluteFilePath("rooms")); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char * byteArray; quint32 length; in.readBytes(byteArray, length); QByteArray arr = QByteArray::fromRawData(byteArray, length); Room m = RoomModel::fromJSon(QJsonDocument::fromBinaryData(arr).object()); addRoom(m.id, m.name, m.selected); } } } qDebug() << "Cache Loaded"; } QHash RoomModel::roleNames() const { QHash roles; roles[RoomName] = "name"; roles[RoomID] = "room_id"; roles[RoomSelected] = "selected"; + roles[RoomUnread] = "unread"; return roles; } int RoomModel::rowCount(const QModelIndex & parent) const { // if (m_roomsHash.size() > 4) {return 4;} // qDebug() << m_roomsList.size() << "ROOMS"; return m_roomsList.size(); } QVariant RoomModel::data(const QModelIndex & index, int role) const { // qDebug() << "GOT ASKED FOR " << index.row(); Room r = m_roomsList.at(index.row()); if (role == RoomModel::RoomName) { return r.name; } else if (role == RoomModel::RoomID) { return r.id; } else if (role == RoomModel::RoomSelected) { return r.selected; } else { return QVariant("0"); } } // void RoomModel::setActiveRoom(const QString& activeRoom) // { // foreach (const QString &id, m_roomsHash.keys()) { // qDebug() << id; // m_roomsHash[id].selected = (id == activeRoom); // } // // emit dataChanged(createIndex(1, 1), createIndex(rowCount(), 1)); // } void RoomModel::addRoom(const QString& roomID, const QString& roomName, bool selected) { if (roomID.isEmpty() || roomName.isEmpty()) { return; } qDebug() << "Adding room" << roomID << roomName; Room r; r.id = roomID; r.name = roomName; r.selected = selected; addRoom(r); } void RoomModel::addRoom(const Room &room) { auto existingRoom = qFind(m_roomsList.begin(), m_roomsList.end(), room); bool present = (existingRoom != m_roomsList.end()); // qDebug() << "Present? "<< present; auto i = qUpperBound(m_roomsList.begin(), m_roomsList.end(), room); int pos = i-m_roomsList.begin(); bool roomChanged = false; qDebug() << pos; // if (qFind(m_roomsList.begin(), m_roomsList.end(), room) != m_roomsList.end() && pos > 0) { if (present) { // qDebug() << (qFind(m_roomsList.begin(), m_roomsList.end(), room) - m_roomsList.begin()); // if (pos != m_roomsList.size()) { // we're at the end // qDebug() << "Room changed!"; roomChanged = true; //Figure out a better way to update just the really changed message } else { beginInsertRows(QModelIndex(), pos, pos); } if (roomChanged) { m_roomsList.replace(pos-1, room); } else { qDebug() << "Inserting room at position" <getModelForRoom(room.id); } // #include "roommodel.moc" diff --git a/src/roommodel.h b/src/roommodel.h index 3321060f..a065d6bd 100644 --- a/src/roommodel.h +++ b/src/roommodel.h @@ -1,88 +1,92 @@ /* * * Copyright 2016 Riccardo Iaconelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef ROOMMODEL_H #define ROOMMODEL_H #include class Room { public: // To be used in ID find: message ID inline bool operator==(const Room &other) const { return other.id == id; }; // To be used in sorted insert: timestamp inline bool operator<(const Room &other) const { return name < other.name; }; - + +// When you add a field, please remember to also add relevant code +// to the enum declaration, roleNames, fromJSon and serialize QString name; QString topic; + int unread; bool selected = false; QString id; }; class RoomModel : public QAbstractListModel { Q_OBJECT public: enum RoomRoles { RoomName = Qt::UserRole + 1, RoomSelected, - RoomID + RoomID, + RoomUnread }; RoomModel (QObject *parent = 0); virtual ~RoomModel(); virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; // void setCurrentRoom(const QString &newRoom); // QString getCurrentRoom() const; Q_INVOKABLE void addRoom(const QString& roomID, const QString& roomName, bool selected = false); void addRoom(const Room& room); static Room fromJSon(const QJsonObject &source); static QByteArray serialize(const Room &r); // void setActiveRoom(const QString &activeRoom); void reset(); void clear(); protected: virtual QHash roleNames() const; private: QVector m_roomsList; // QHash< QString, Room > m_roomsHash; }; #endif // ROOMMODEL_H diff --git a/src/userdata.cpp b/src/userdata.cpp index 37b3783a..7e55cafc 100644 --- a/src/userdata.cpp +++ b/src/userdata.cpp @@ -1,212 +1,212 @@ /* * * Copyright 2016 Riccardo Iaconelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "userdata.h" #include "roommodel.h" #include "ddpclient.h" UserData *UserData::m_self = 0; QString UserData::authToken() const { return m_authToken; } QString UserData::userName() const { return m_userName; } QString UserData::password() const { return m_password; } void UserData::setAuthToken(const QString& token) { qDebug() << "Setting token to" << token; QSettings s; m_authToken = token; s.setValue("authToken", token); } void UserData::setPassword(const QString& password) { m_password = password; } void UserData::setUserName(const QString& username) { m_userName = username; QSettings s; s.setValue("username", username); emit userNameChanged(); } RoomModel * UserData::roomModel() { if (!m_roomModel) { qDebug() << "creating new RoomModel"; m_roomModel = new RoomModel(this); qDebug() << m_roomModel; // m_roomModel->reset(); } return m_roomModel; } DDPClient * UserData::ddp() { if (!m_ddp) { m_ddp = new DDPClient(serverURL()); connect(m_ddp, &DDPClient::loginStatusChanged, this, &UserData::loginStatusChanged); // connect(m_ddp, &DDPClient::loginStatusChanged, this, [=](){qDebug() << "Signal received";}); } return m_ddp; } void UserData::sendMessage(const QString &roomID, const QString &message) { QString json = "{\"rid\": \"%1\", \"msg\": \"%2\"}"; json = json.arg(roomID, message); ddp()->method("sendMessage", QJsonDocument::fromJson(json.toUtf8())); } MessageModel * UserData::getModelForRoom(const QString& roomID) { if (m_messageModels.contains(roomID)) { // qDebug() << "Returning old model for " << roomID; return m_messageModels.value(roomID); } else { - qDebug() << "Creating a new model"; +// qDebug() << "Creating a new model"; m_messageModels[roomID] = new MessageModel(roomID, this); return m_messageModels[roomID]; } } QString UserData::serverURL() const { return m_serverURL; } void UserData::setServerURL(const QString& serverURL) { if (m_serverURL == serverURL) { return; } QSettings s; s.setValue("serverURL", serverURL); m_serverURL = serverURL; // m_roomModel->reset(); emit serverURLChanged(); } DDPClient::LoginStatus UserData::loginStatus() { if (m_ddp) { return ddp()->loginStatus(); } else { return DDPClient::LoggedOut; } } void UserData::tryLogin() { qDebug() << "Attempting login" << userName() << "on" << serverURL(); // Reset model views foreach (const QString key, m_messageModels.keys()) { MessageModel *m = m_messageModels.take(key); delete m; } delete m_ddp; m_ddp = 0; // In the meantime, load cache... m_roomModel->reset(); // This creates a new ddp() object. // DDP will automatically try to connect and login. ddp(); } void UserData::logOut() { setAuthToken(QString()); setPassword(QString()); // m_ddp->logOut(); foreach (const QString key, m_messageModels.keys()) { MessageModel *m = m_messageModels.take(key); delete m; } delete m_ddp; m_ddp = 0; emit loginStatusChanged(); m_roomModel->clear(); // m_roomModel->reset(); // RoomModel -> reset(); } QString UserData::cacheBasePath() const { if (m_serverURL.isEmpty()) { return QString(); } return QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/'+m_serverURL; } // QString UserData::activeRoom() const // { // return m_activeRoom; // } // void UserData::setActiveRoom(const QString& activeRoom) // { // m_activeRoom = activeRoom; // // roomModel()->setActiveRoom(activeRoom); // emit activeRoomChanged(); // } UserData::UserData(QObject* parent) : QObject(parent), m_ddp(0), m_roomModel(0) { QSettings s; m_serverURL = s.value("serverURL", "demo.rocket.chat").toString(); m_userName = s.value("username").toString(); m_authToken = s.value("authToken").toString(); // roomModel()->reset(); } UserData * UserData::self() { if (!m_self) { m_self = new UserData; m_self->ddp(); // Create DDP object so we try to connect at startup m_self->roomModel()->reset(); // m_self->getModelForRoom("GENERAL"); } return m_self; }