/******************************************************************************************** * * * Copyright (C) 2017 Armin Felder, Dennis Beier * * This file is part of RocketChatMobileEngine . * * * * RocketChatMobileEngine 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 3 of the License, or * * (at your option) any later version. * * * * RocketChatMobileEngine 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 RocketChatMobileEngine. If not, see . * * * ********************************************************************************************/ #include "rocketchatserver.h" #include "CustomModels/models.h" RocketChatServerData::RocketChatServerData( QString pId, QString pBaseUrl, bool pUnsecure ): mBaseUrl( std::move( pBaseUrl ) ), mServerId( std::move( pId ) ), userModel( Models::getUsersModel() ), loginMethodsModel( Models::getLoginMethodsModel() ), channelsModel( Models::getPublicGroupsModel() ), directModel( Models::getDirectChannelsModel() ), groupsModel( Models::getPublicGroupsModel() ) { qRegisterMetaType( "User" ); qRegisterMetaType>>( "QList>" ); mUnsecureConnection = pUnsecure; mApiUri = QStringLiteral( "/api/v1" ); mEmojiRepo = new EmojiRepo( this ); mFilesRepo = new FilesRepo( this ); } void RocketChatServerData::init() { initDb(); } void RocketChatServerData::initDb() { mStorage = PersistanceLayer::instance(); connect( mStorage, &PersistanceLayer::ready, this, &RocketChatServerData::initConnections, Qt::UniqueConnection ); mStorage->init(); } void RocketChatServerData::initConnections() { historyLoaded = [ = ]( QMultiMap> *messages ) { if ( messages ) { mStorage->transaction(); for ( auto it = messages->begin(); it != messages->end(); it++ ) { if ( Q_LIKELY( mChannels->contains( it.key() ) && !mChannels->get( it.key() ).isNull() ) ) { MessageList messageList = messages->values( it.key() ); auto channel = mChannels->get( it.key() ); QList> newones = channel->addMessages( messageList ); mMessageService->deleteMessagesNotInList( messages, it.key(), true ); if ( newones.count() ) { mMessageService->persistMessages( newones ); } } } mStorage->askForcommit(); delete messages; } }; QString protocol = QStringLiteral( "https://" ); if ( mUnsecureConnection ) { protocol = QStringLiteral( "http://" ); } if ( mRestApi != nullptr ) { mRestApi->deleteLater(); } if ( mFileService != nullptr ) { delete mFileService; } if ( mEmojiService != nullptr ) { delete mEmojiService; } if ( mDdpApi != nullptr ) { mDdpApi->deleteLater(); } if ( mChannels != nullptr ) { mChannels->deleteLater(); } if ( mMessageService != nullptr ) { mMessageService->deleteLater(); } if ( mChannelService != nullptr ) { mChannelService->deleteLater(); } setRestApi( new RestApi( this, protocol + mBaseUrl ) ); mFileService = new FileService( this ); mEmojiService = new EmojiService( this, mFileService ); mDdpApi = new MeteorDDP( this, mBaseUrl, mUnsecureConnection ); mRestApi->moveToThread( QThread::currentThread() ); mDdpApi->moveToThread( QThread::currentThread() ); mChannels = new ChannelRepository( this ); RocketChatServerConfig::init(); auto result = QMetaObject::invokeMethod( mRestApi, "init" ); Q_ASSERT( result ); mDdpApi->registerMessageHandler( this ); connect( mDdpApi, &MeteorDDP::ddpConnected, this, &RocketChatServerData::onDDPConnected, Qt::UniqueConnection ); connect( mDdpApi, &MeteorDDP::ddpDisconnected, this, &RocketChatServerData::onDDPDisonnected, Qt::UniqueConnection ); QPair tokenDb = mStorage->getToken(); QString token = tokenDb.first; QString userDb = mStorage->getUserName(); if ( !token.isEmpty() && !userDb.isEmpty() ) { emit offlineMode(); } setUserId( mStorage->getUserId() ); mUsername = mStorage->getUserName(); loadEmojis(); mMessageService = new MessageService( this, mStorage, this, mEmojiRepo, mFileService ); mChannelService = new RocketChatChannelService( this, this, mMessageService, mFileService ); mChannelService->setDdp( mDdpApi ); mChannelService->setChannels( mChannels ); connect( mChannelService, &RocketChatChannelService::channelsLoaded, this, &RocketChatServerData::onChannelsLoaded, Qt::UniqueConnection ); connect( mChannelService, &RocketChatChannelService::usersLoaded, this, &RocketChatServerData::onUsersLoaded, Qt::UniqueConnection ); connect( mChannelService, &RocketChatChannelService::directChannelReady, this, &RocketChatServerData::switchChannelByName, Qt::UniqueConnection ); connect( channelsModel, &ChannelModel::unreadMessagesChanged, this, &RocketChatServerData::onUnreadCountChanged, Qt::UniqueConnection ); connect( directModel, &ChannelModel::unreadMessagesChanged, this, &RocketChatServerData::onUnreadCountChanged, Qt::UniqueConnection ); connect( groupsModel, &ChannelModel::unreadMessagesChanged, this, &RocketChatServerData::onUnreadCountChanged, Qt::UniqueConnection ); QString lastServer = mStorage->getSetting( QStringLiteral( "currentServer" ) ); if ( lastServer == mBaseUrl ) { QPair tokenDb = mStorage->getToken(); QString userDb = mStorage->getUserName(); QDateTime currentTime = QDateTime::currentDateTime(); //TODO: needs more testing if ( !tokenDb.first.isEmpty() && tokenDb.second > currentTime.toTime_t() ) { loginWithToken( userDb, tokenDb.first ); } else { wipeDbAndReconnect(); emit loggedOut( mServerId ); } } else if ( !lastServer.isEmpty() ) { wipeDbAndReconnect(); emit loggedOut( mServerId ); } emit readyToCheckForPendingNotification(); mChannelService->loadJoinedChannelsFromDb(); } void RocketChatServerData::setRestApi( RestApi *pNewRestApi ) { connect( pNewRestApi, &RestApi::loggedIn, this, &RocketChatServerData::onLoggedIn, Qt::UniqueConnection ); connect( pNewRestApi, &RestApi::loginError, this, &RocketChatServerData::onLoginError, Qt::UniqueConnection ); this->mRestApi = pNewRestApi; QPair token = mStorage->getToken(); if ( token.first.length() && token.second > 0 ) { mResumeToken = token.first; mTokenExpire = token.second; } } void RocketChatServerData::loadEmojis() { /* QFile file(":/emoji.json"); if(!file.open(QFile::ReadOnly)){ qDebug()<"; QString unicode = Utils::escapeUnicodeEmoji(emojiObj["unicode"].toString()); QString category = emojiObj["category"].toString(); QString sort_order = emojiObj["emoji_order"].toString(); int sort_orderInt = sort_order.toInt(); qDebug()<addCustomEmoji(id,file,html,category,unicode,sort_orderInt); }*/ auto emojiModel = Models::getEmojisModel(); auto emojiList = mEmojiService->loadEmojisFromDb(); QHash>> emojisByCategory; for ( auto &emoji : emojiList ) { if ( mEmojiRepo != nullptr && !emoji.isNull() ) { mEmojiRepo->add( emoji->getIdentifier(), emoji ); } auto category = emoji->getCategory(); if ( !emojisByCategory.contains( category ) ) { emojisByCategory.insert( category, QList>() ); emojisByCategory[category].reserve( 500 ); if ( category == "custom" ) { mCustomEmojisReady = true; } } emojisByCategory[category].append( std::move( emoji ) ); } for ( auto it = emojisByCategory.begin(); it != emojisByCategory.end(); it++ ) { auto result = QMetaObject::invokeMethod( emojiModel, "addEmojisByCategory", Q_ARG( QString, it.key() ), Q_ARG( QList>, std::move( it.value() ) ) ); Q_ASSERT( result ); } } void RocketChatServerData::switchChannel( const QString &pServer, const QString &pRid, const QString &pName, const QString &pType ) { Q_UNUSED( pServer ) qDebug() << "switch channel to: " << pRid; bool switchPossible = false; QSharedPointer channel = nullptr; if ( pRid.length() ) { if ( mChannels != nullptr && !mChannels->contains( pRid ) ) { if ( pType == "d" || pType == "c" || pType == "p" ) { qDebug() << "create new channel object to:" << pRid; mChannelService->createChannelObject( pRid, pName, pType ); if ( mChannels->contains( pRid ) && !mChannels->get( pRid ).isNull() ) { channel = mChannels->get( pRid ); switchPossible = true; } } } else if ( mChannels != nullptr ) { channel = mChannels->get( pRid ); switchPossible = true; } } if ( switchPossible ) { qDebug() << "current Channel " << mCurrentChannel; qDebug() << "room " << pRid; if ( mCurrentChannel != pRid ) { emit channelSwitchRequest( channel ); } } } void RocketChatServerData::switchChannelByName( const QString &pName, const QString &pType ) { if ( mLoggedIn ) { if ( mChannels->getChannelByName( pName ) ) { auto channel = mChannels->getChannelByName( pName ); switchChannel( QStringLiteral( "default" ), channel->getRoomId(), pName, channel->getType() ); mPendingSwitchRoomRequest.clear(); mPendingSwitchRoomRequestType.clear(); } else { DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ) { if ( pResponse.contains( "result" ) ) { QJsonObject result = pResponse["result"].toObject(); QString id = result["_id"].toString(); switchChannel( QStringLiteral( "default" ), id, pName, pType ); } }; auto request = QSharedPointer::create( pName, pType ); request->setSuccess( success ); sendDdprequest( request ); //searchForRoomIdByName( pName ); } } else { mPendingSwitchRoomRequest = pName; mPendingSwitchRoomRequestType = pType; } } void RocketChatServerData::requestGetChannelDetails( const QString &pChannelId ) { if ( mChannels->contains( pChannelId ) ) { auto channel = mChannels->get( pChannelId ); if ( !channel.isNull() ) { auto archived = channel->getArchived(); auto readonly = channel->getReadOnly(); auto muted = channel->getMuted(); auto selfMuted = channel->getSelfMuted(); QVariantList mutedList; for ( const auto &entry : muted ) { mutedList.append( entry ); } QVariantMap details; details[QStringLiteral( "archived" )] = archived; details[QStringLiteral( "ro" )] = readonly; details[QStringLiteral( "muted" )] = mutedList; details[QStringLiteral( "selfMuted" )] = selfMuted; details[QStringLiteral( "type" )] = channel->getType(); details[QStringLiteral( "name" )] = channel->getName(); details[QStringLiteral( "ownerId" )] = channel->getOwnerId(); details[QStringLiteral( "ownerName" )] = channel->getOwnerName(); details[QStringLiteral( "blocked" )] = channel->getBlocked(); emit channelDetailsReady( details, pChannelId ); } } } void RocketChatServerData::requestIsLoggedIn() { if ( mLoggedIn ) { emit loggedIn( QStringLiteral( "default" ) ); } } void RocketChatServerData::setUserPresenceDefaultStatus( int pStatus ) { if ( pStatus >= 0 && pStatus < 5 && isWebsocketValid() && diffToLastDDPPing() < 29 ) { auto request = QSharedPointer::create( static_cast( pStatus ) ); sendDdprequest( request, true ); } } void RocketChatServerData::setUserPresenceStatus( int pStatus ) { if ( pStatus >= 0 && pStatus < 5 && isWebsocketValid() && diffToLastDDPPing() < 29 ) { auto request = QSharedPointer::create( static_cast( pStatus ) ); sendDdprequest( request, true ); } } void RocketChatServerData::onStateChanged( const Qt::ApplicationState &pState ) { Q_UNUSED( pState ) if ( !isWebsocketValid() || diffToLastDDPPing() > 29 ) { qDebug() << "call resume"; resume(); } else { setUserPresenceStatus( static_cast( RocketChatUser::status::ONLINE ) ); } } void RocketChatServerData::createVideoCall( const QString &pRid ) { if ( !pRid.isEmpty() ) { auto request = QSharedPointer::create( pRid ); sendDdprequest( request ); joinJitsiCall( pRid ); } } //TODO: rename, as method is not really for searching only void RocketChatServerData::searchForRoomIdByName( const QString &pName ) { auto request = QSharedPointer::create( pName ); std::function success = [ = ]( QNetworkReply *, QJsonObject data, RestApi * ) { if ( data.contains( QStringLiteral( "result" ) ) ) { QJsonObject result = data[QStringLiteral( "result" )].toObject(); if ( result.contains( QStringLiteral( "users" ) ) ) { QJsonArray users = result[QStringLiteral( "users" )].toArray(); for ( auto user : users ) { QJsonObject userObj = user.toObject(); if ( userObj.contains( QStringLiteral( "username" ) ) && userObj.contains( QStringLiteral( "_id" ) ) ) { QString username = userObj[QStringLiteral( "username" )].toString(); if ( !username.compare( pName, Qt::CaseInsensitive ) ) { QString id = userObj[QStringLiteral( "_id" )].toString(); switchChannel( QStringLiteral( "default" ), id, pName, "d" ); } } } } else if ( result.contains( QStringLiteral( "rooms" ) ) ) { QJsonArray rooms = result[QStringLiteral( "rooms" )].toArray(); for ( const auto room : rooms ) { QJsonObject roomObj = room.toObject(); if ( roomObj.contains( QStringLiteral( "name" ) ) && roomObj.contains( QStringLiteral( "_id" ) ) ) { QString roomname = roomObj[QStringLiteral( "name" )].toString(); if ( !roomname.compare( pName, Qt::CaseInsensitive ) ) { QString id = roomObj[QStringLiteral( "_id" )].toString(); switchChannel( QStringLiteral( "default" ), id, pName, "c" ); } } } } } mPendingSwitchRoomRequest.clear(); mPendingSwitchRoomRequestType.clear(); }; request->setSuccess( success ); sendApiRequest( request ); } void RocketChatServerData::blockUser( const QString &pChannelId ) { auto userId = pChannelId; userId.replace( mUserId, "" ); if ( userId == pChannelId ) { QString possibleRoom1 = userId + mUserId; QString possibleRoom2 = mUserId + userId; auto blockRequest1 = QSharedPointer::create( possibleRoom1, userId ); auto blockRequest2 = QSharedPointer::create( possibleRoom2, userId ); sendDdprequest( blockRequest1 ); sendDdprequest( blockRequest2 ); } else { auto blockRequest = QSharedPointer::create( pChannelId, userId ); sendDdprequest( blockRequest ); } } void RocketChatServerData::unBlockUser( const QString &pChannelId ) { auto userId = pChannelId; userId.replace( mUserId, "" ); if ( userId == pChannelId ) { QString possibleRoom1 = userId + mUserId; QString possibleRoom2 = mUserId + userId; auto unBlockRequest1 = QSharedPointer::create( possibleRoom1, userId ); auto unBlockRequest2 = QSharedPointer::create( possibleRoom2, userId ); sendDdprequest( unBlockRequest1 ); sendDdprequest( unBlockRequest2 ); } else { auto unBlockRequest = QSharedPointer::create( pChannelId, userId ); sendDdprequest( unBlockRequest ); } } QString RocketChatServerData::getUserId() const { return mUserId; } void RocketChatServerData::setUserId( const QString &userId ) { auto users = Models::getUsersModel(); auto user = users->getUserById( userId ); if ( user.isNull() && !userId.isEmpty() ) { user = QSharedPointer::create( userId ) ; //QSharedPointer( new RocketChatUser( userId ) ); auto result = QMetaObject::invokeMethod( userModel, "addUser", Q_ARG( User, user ) ); Q_ASSERT( result ); } if ( !user.isNull() && !userId.isEmpty() ) { mUserId = userId; connect( user.data(), &RocketChatUser::statusChanged, this, [ = ]() { emit userStatusChanged( static_cast( user->getStatus() ) ); } ); } } void RocketChatServerData::disconnectFromServer() { mConnectionState = ConnectionState::OFFLINE; emit offline(); mDdpApi->disconnectFromServer(); } void RocketChatServerData::loadHistories() { QStringList channelIds; for ( const auto ¤tChannel : mChannels->getElements() ) { if ( Q_LIKELY( !currentChannel.isNull() && currentChannel->getJoined() ) ) { //joinChannel( currentChannel->getRoomId() ); channelIds.append( currentChannel->getRoomId() ); } } auto request = QSharedPointer::create( channelIds, 1 ); request->setLimit( 1 ); request->setSuccess( historyLoaded ); request->setNoLastThreeDays( true ); mMessageService->loadHistory( request ); } void RocketChatServerData::loadHistoryTill( const QString &pChannelId, qint64 pTs ) { std::function> *messages )> success = [ = ]( QMultiMap> *messages ) { historyLoaded( messages ); }; auto request = QSharedPointer::create( pChannelId ); request->setEnd( pTs ); request->setLimit( 0 ); request->setSuccess( success ); request->setNoLastThreeDays( true ); request->setStart( 0 ); request->setSource( LoadHistoryServiceRequest::Source::SERVER ); mMessageService->loadHistory( request ); } void RocketChatServerData::onUsersLoaded( const QString &pChannelId, const QVector > &pUserList ) { if ( Q_LIKELY( mChannels->contains( pChannelId ) ) ) { auto channel = mChannels->get( pChannelId ); if ( !channel.isNull() ) { for ( const auto &user : pUserList ) { if ( !user.isNull() ) { channel->addUser( user ); auto result = QMetaObject::invokeMethod( userModel, "insertUser", Q_ARG( QString, pChannelId ), Q_ARG( User, user ) ); Q_ASSERT( result ); } } } } } void RocketChatServerData::onUnreadCountChanged() { static int lastUnreadCount = -1; uint number = 0; qDebug() << "on Unread count changed"; for ( const auto &channel : mChannels->getElements() ) { if ( !channel.isNull() ) { number += channel->getUnreadMessages(); } } if ( lastUnreadCount != number ) { lastUnreadCount = number; emit unreadCountChanged( QStringLiteral( "default" ), number ); } } bool RocketChatServerData::handlesMessage( const QJsonObject &message ) { qDebug() << "RocketChatServerData message handler"; onDDPMessageReceived( message ); return true; } EmojiRepo *RocketChatServerData::getEmojiRepo() const { return mEmojiRepo; } void RocketChatServerData::setEmojiRepo( EmojiRepo *emojiRepo ) { mEmojiRepo = emojiRepo; } void RocketChatServerData::createAccount( const QString &username, const QString &email, const QString &password ) { auto request = QSharedPointer::create( username, email, password ); sendDdprequest( request ); } void RocketChatServerData::offlineLogin() { QString userId = mStorage->getUserId(); QString userName = mStorage->getUserName(); if ( !userId.isEmpty() && !userName.isEmpty() ) { setUserId( userId ); emit loggedIn( QStringLiteral( "default" ) ); } } void RocketChatServerData::wipeDbAndReconnect() { disconnectFromServer(); mStorage->wipeDb(); resume(); } void RocketChatServerData::reconnect() { disconnectFromServer();; resume(); } void RocketChatServerData::deleteMessage( QString rid, QString id ) { QJsonArray params = { QJsonObject( {{"_id", id }} )}; auto request = QSharedPointer::create( "deleteMessage", params ); DdpCallback success = [ = ]( QJsonObject, MeteorDDP * ) { try { auto room = mChannels->get( rid ); mStorage->deleteMessage( id ); room->deleteMessage( id ); } catch ( std::logic_error error ) { qDebug() << error.what(); } }; request->setSuccess( success ); sendDdprequest( request ); } QSharedPointer RocketChatServerData::getOwnUser() const { return mOwnUser; } void RocketChatServerData::setOwnUser( const QSharedPointer &ownUser ) { mOwnUser = ownUser; } QString RocketChatServerData::getBaseUrl() const { QString protocol = "https://"; if ( mDdpApi->getUnsecure() ) { protocol = "http://"; } return protocol + mBaseUrl; } void RocketChatServerData::login( const QString &pUsername, const QString &pPassword ) { QString pass; QByteArray hashArray = QCryptographicHash::hash( pPassword.toUtf8(), QCryptographicHash::Sha256 ); pass = hashArray.toHex(); loginWithHash( pUsername, pass ); } void RocketChatServerData::loginWithHash( const QString &pUsername, const QString &pPswHash ) { qDebug() << "login with hash"; RestRequestCallback meCallBackSuccess = [ = ]( QNetworkReply *, QJsonObject data, RestApi * ) { if ( data.contains( QStringLiteral( "username" ) ) ) { mUsername = data[QStringLiteral( "username" )].toString(); mStorage->transaction(); mStorage->setUserData( mUsername, "" ); mStorage->setToken( mResumeToken, mTokenExpire ); mStorage->setUserId( mUserId ); mStorage->askForcommit(); onResume(); onDDPAuthenticated(); } }; auto request = QSharedPointer::create( pUsername, pPswHash ); DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ) { qDebug() << "authenticated"; if ( pResponse.contains( QStringLiteral( "result" ) ) ) { QJsonObject result = pResponse[QStringLiteral( "result" )].toObject(); if ( result.contains( QStringLiteral( "token" ) ) && result.contains( QStringLiteral( "id" ) ) ) { mConnectionState = ConnectionState::ONLINE; mResumeToken = result[QStringLiteral( "token" )].toString(); QJsonObject expireObject = result[QStringLiteral( "tokenExpires" )].toObject(); double expireDouble = expireObject[QStringLiteral( "$date" )].toDouble(); mTokenExpire = static_cast( expireDouble / 1000 ); QString userId = result[QStringLiteral( "id" )].toString(); setUserId( userId ); mStorage->transaction(); mStorage->setUserData( pUsername, pPswHash ); mRestApi->setToken( mResumeToken ); mRestApi->setUserId( userId ); mStorage->askForcommit(); mDdpApi->setToken( mResumeToken ); mDdpApi->unsetResponseBinding( request->getFrame() ); RestApiRequest meRequest = RestApiRequest( new restMeRequest( meCallBackSuccess ) ); mRestApi->sendRequest( meRequest ); } } }; DdpCallback error = [ = ]( QJsonObject pResponse, MeteorDDP * ) { Q_UNUSED( pResponse ); onLoginError(); }; request->setSuccess( success ); request->setError( error ); sendDdprequest( request, true, true ); emit loggingIn(); this->mUsername = pUsername; } void RocketChatServerData::loginWithToken( const QString &pUsername, const QString &pToken, bool pResume ) { Q_UNUSED( pResume ); qDebug() << "login with token"; qDebug() << "token from db: " << pToken; if ( !pToken.isEmpty() ) { auto self = this; RestRequestCallback meCallBackSuccess = [ self ]( QNetworkReply *, QJsonObject data, RestApi * ) { QString username; if ( data.contains( QStringLiteral( "username" ) ) ) { username = data[QStringLiteral( "username" )].toString(); } else if ( data.contains( "name" ) ) { QString name = data["name"].toString().toLower(); QStringList nameParts = name.split( " " ); username = nameParts.join( "." ); } if ( username.length() ) { self->mUsername = username; self->mStorage->transaction(); self->mStorage->setUserData( self->mUsername, "" ); self->mStorage->setToken( self->mResumeToken, self->mTokenExpire ); self->mStorage->setUserId( self->mUserId ); self->onResume(); self->onDDPAuthenticated(); } else { qWarning() << "no valid user information, check message from server"; } }; auto request = QSharedPointer::create( pToken ); DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ) { qDebug() << "authenticated"; mConnectionState = ConnectionState::ONLINE; if ( pResponse.contains( QStringLiteral( "result" ) ) ) { QJsonObject result = pResponse[QStringLiteral( "result" )].toObject(); if ( result.contains( QStringLiteral( "token" ) ) && result.contains( QStringLiteral( "id" ) ) ) { mResumeToken = result[QStringLiteral( "token" )].toString(); QJsonObject expireObject = result[QStringLiteral( "tokenExpires" )].toObject(); double expireDouble = expireObject[QStringLiteral( "$date" )].toDouble(); mTokenExpire = static_cast( expireDouble / 1000 ); QString userId = result[QStringLiteral( "id" )].toString(); setUserId( userId ); mStorage->setToken( self->mResumeToken, self->mTokenExpire ); mStorage->setUserId( userId ); mRestApi->setToken( self->mResumeToken ); mRestApi->setUserId( userId ); mDdpApi->setToken( self-> mResumeToken ); mDdpApi->unsetResponseBinding( request->getFrame() ); RestApiRequest meRequest = RestApiRequest( new restMeRequest( meCallBackSuccess ) ); mRestApi->sendRequest( meRequest ); } } }; DdpCallback error = [ = ]( QJsonObject pResponse, MeteorDDP * ) { Q_UNUSED( pResponse ); qDebug() << "login error"; //onLoginError(); //try again with hash QString userDb = mStorage->getUserName(); QString passDb = mStorage->getPassword(); if ( !userDb.isEmpty() && !passDb.isEmpty() ) { loginWithHash( userDb, passDb ); } else { qWarning() << "token rejected and no user+hash information present"; onLoginError(); } }; request->setSuccess( success ); request->setError( error ); #ifdef Q_OS_IOS mDdpApi->sendRequest( request ); #else sendDdprequest( request, true, true ); #endif emit loggingIn(); this->mUsername = pUsername; } else { emit loginError(); qDebug() << "empty token"; } } void RocketChatServerData::loginWtihSamlToken( const QString &pToken ) { if ( !pToken.isEmpty() ) { qDebug() << pToken; auto self = this; RestRequestCallback meCallBackSuccess = [ self ]( QNetworkReply *, QJsonObject data, RestApi * ) { QString username; if ( data.contains( QStringLiteral( "username" ) ) ) { username = data[QStringLiteral( "username" )].toString(); } else if ( data.contains( "name" ) ) { QString name = data["name"].toString().toLower(); QStringList nameParts = name.split( " " ); username = nameParts.join( "." ); } if ( username.length() ) { self->mUsername = username; self->mStorage->transaction(); self->mStorage->setUserData( self->mUsername, "" ); self->mStorage->setToken( self->mResumeToken, self->mTokenExpire ); self->mStorage->setUserId( self->mUserId ); self->mStorage->askForcommit(); self->onResume(); self->onDDPAuthenticated(); } else { qWarning() << "no valid user information, check message from server"; } }; auto request = QSharedPointer::create( pToken ); DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ) { qDebug() << "authenticated"; mConnectionState = ConnectionState::ONLINE; if ( pResponse.contains( QStringLiteral( "result" ) ) ) { QJsonObject result = pResponse[QStringLiteral( "result" )].toObject(); if ( result.contains( QStringLiteral( "token" ) ) && result.contains( QStringLiteral( "id" ) ) ) { this->mResumeToken = result[QStringLiteral( "token" )].toString(); QJsonObject expireObject = result[QStringLiteral( "tokenExpires" )].toObject(); double expireDouble = expireObject[QStringLiteral( "$date" )].toDouble(); mTokenExpire = static_cast( expireDouble / 1000 ); QString userId = result[QStringLiteral( "id" )].toString(); setUserId( userId ); mStorage->setToken( mResumeToken, mTokenExpire ); mStorage->setUserId( userId ); mRestApi->setToken( mResumeToken ); mRestApi->setUserId( userId ); mDdpApi->setToken( mResumeToken ); mDdpApi->unsetResponseBinding( request->getFrame() ); RestApiRequest meRequest = RestApiRequest( new restMeRequest( meCallBackSuccess ) ); self->sendApiRequest( meRequest, true ); } } }; DdpCallback error = [ = ]( QJsonObject pResponse, MeteorDDP * ) { Q_UNUSED( pResponse ); qDebug() << "login error"; qWarning() << "SAML2 token rejected"; onLoginError(); }; request->setSuccess( success ); request->setError( error ); sendDdprequest( request, true, true ); emit loggingIn(); } else { qDebug() << "empty token"; } } void RocketChatServerData::loginWithOpenIDToken( const QString &pToken, const QString &pSecret ) { if ( !pToken.isEmpty() && !pSecret.isEmpty() ) { auto self = this; RestRequestCallback meCallBackSuccess = [ self ]( QNetworkReply *, QJsonObject data, RestApi * ) { QString username; if ( data.contains( QStringLiteral( "username" ) ) ) { username = data[QStringLiteral( "username" )].toString(); } else { DdpCallback nameSuggestionSucces = [ = ]( QJsonObject pResponse, MeteorDDP * ) { if ( pResponse.contains( "result" ) ) { QString username = pResponse["result"].toString(); DdpCallback setUsernameSuccess = [ = ]( QJsonObject pResponse, MeteorDDP * ) { if ( pResponse.contains( "result" ) ) { QString username = pResponse["result"].toString(); self->mUsername = username; self->mStorage->transaction(); self->mStorage->setUserData( self->mUsername, "" ); self->mStorage->setToken( self->mResumeToken, self->mTokenExpire ); self->mStorage->setUserId( self->mUserId ); self->mStorage->askForcommit(); self->onResume(); self->onDDPAuthenticated(); } }; QSharedPointer setUsernameRequest( new RocketChatSetUsername( username, setUsernameSuccess ) ); self->sendDdprequest( setUsernameRequest, true ); } }; QSharedPointer nameSuggestionRequest( new RocketChatGetUsernameSuggestion( nameSuggestionSucces ) ); self->sendDdprequest( nameSuggestionRequest, true ); } if ( username.length() ) { self->mUsername = username; self->mStorage->transaction(); self->mStorage->setUserData( self->mUsername, "" ); self->mStorage->setToken( self->mResumeToken, self->mTokenExpire ); self->mStorage->setUserId( self->mUserId ); self->mStorage->askForcommit(); self->onResume(); self->onDDPAuthenticated(); } else { qWarning() << "no valid user information, check message from server"; } }; auto request = QSharedPointer::create( pToken, pSecret ); DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ) { qDebug() << "authenticated"; self->mConnectionState = ConnectionState::ONLINE; if ( pResponse.contains( QStringLiteral( "result" ) ) ) { QJsonObject result = pResponse[QStringLiteral( "result" )].toObject(); if ( result.contains( QStringLiteral( "token" ) ) && result.contains( QStringLiteral( "id" ) ) ) { mConnectionState = ConnectionState::ONLINE; mResumeToken = result[QStringLiteral( "token" )].toString(); QJsonObject expireObject = result[QStringLiteral( "tokenExpires" )].toObject(); double expireDouble = expireObject[QStringLiteral( "$date" )].toDouble(); mTokenExpire = static_cast( expireDouble / 1000 ); QString userId = result[QStringLiteral( "id" )].toString(); setUserId( userId ); mRestApi->setToken( mResumeToken ); mRestApi->setUserId( userId ); mDdpApi->setToken( mResumeToken ); mDdpApi->unsetResponseBinding( request->getFrame() ); // emit onHashLoggedIn( self->mServerId ) ; RestApiRequest meRequest = RestApiRequest( new restMeRequest( meCallBackSuccess ) ); self->sendApiRequest( meRequest, true ); } } }; DdpCallback error = [ = ]( QJsonObject pResponse, MeteorDDP * ) { Q_UNUSED( pResponse ); qDebug() << "login error"; qWarning() << "token rejected and no user+hash information present"; onLoginError(); }; request->setSuccess( success ); request->setError( error ); sendDdprequest( request, true, true ); emit loggingIn(); } else { qDebug() << "empty token"; } } void RocketChatServerData::loginWithMethod( const QString &method, const QString &payload ) { if ( mLoginMethodRepo.contains( method ) ) { LoginMethod loginMethod = mLoginMethodRepo[method]; if ( loginMethod.getType() == LoginMethod::Type::OPENID ) { QString token; QString secret; QJsonDocument doc = QJsonDocument::fromJson( payload.toUtf8() ); QJsonObject obj = doc.object(); if ( obj.contains( QStringLiteral( "credentialToken" ) ) && obj.contains( QStringLiteral( "credentialSecret" ) ) ) { token = obj[QStringLiteral( "credentialToken" )].toString(); secret = obj[QStringLiteral( "credentialSecret" )].toString(); // if app went offline during webview disconnectFromServer(); loginWithOpenIDToken( token, secret ); resume(); } } } } void RocketChatServerData::onDDPConnected() { auto request = QSharedPointer::create(); mDdpApi->sendRequest( request ); if ( mResumeOperation ) { auto token = mDdpApi->getToken(); if ( token.isEmpty() ) { token = mStorage->getToken().first; } if ( !token.isEmpty() ) { mDdpApi->setToken( token ); loginWithToken( mUsername, token, true ); } } else { QPair tokenDb = mStorage->getToken(); QString token = tokenDb.first; QString userDb = mStorage->getUserName(); if ( !token.isEmpty() && !userDb.isEmpty() ) { loginWithToken( userDb, token ); emit offlineMode(); } } mConnectionState = ConnectionState::CONNECTED; getServerSettings(); getLoginMethods(); emit( ddpConnected( mServerId ) ); mConnected = true; sendUnsentMessages(); } void RocketChatServerData::onDDPDisonnected() { mConnectionState = ConnectionState::OFFLINE; emit offline(); } void RocketChatServerData::onDDPAuthenticated() { //TODO: dirty fix! getCustomEmojis(); setUserPresenceStatus( static_cast( RocketChatUser::status::ONLINE ) ); mStorage->setSetting( QStringLiteral( "currentServer" ), mBaseUrl ); getServerInfo(); if ( !mCurrentChannel.isEmpty() ) { joinChannel( mCurrentChannel ); } } void RocketChatServerData::handleChannelMessage( const QJsonObject &pMessage ) { if ( Q_LIKELY( pMessage.contains( QStringLiteral( "fields" ) ) ) ) { QJsonObject fields = pMessage[QStringLiteral( "fields" )].toObject(); if ( Q_LIKELY( fields.contains( QStringLiteral( "args" ) ) ) ) { QJsonArray args = fields[QStringLiteral( "args" )].toArray(); QString firstArg = args[0].toString(); if ( firstArg == QStringLiteral( "insert" ) || firstArg == QStringLiteral( "inserted" ) ) { QJsonObject newChannel = args[1].toObject(); if ( newChannel.contains( QStringLiteral( "name" ) ) && ( newChannel.contains( QStringLiteral( "rid" ) ) || newChannel.contains( QStringLiteral( "_id" ) ) ) && newChannel.contains( "t" ) ) { QString name = newChannel[QStringLiteral( "name" )].toString(); QString rid; if ( newChannel.contains( "rid" ) ) { rid = newChannel[QStringLiteral( "rid" )].toString(); } else if ( newChannel.contains( QStringLiteral( "_id" ) ) ) { rid = newChannel[QStringLiteral( "_id" )].toString(); } else { qWarning() << "invalid channel inserted message"; return ; } if ( !mChannels->contains( rid ) ) { auto channel = mChannelService->createChannelObject( rid, name, newChannel["t"].toString() ); if ( !channel.isNull() ) { channel->setJoined( true ); //TODO: check if ( mChannels->add( rid, channel ) ) { } } } } } else if ( firstArg == QStringLiteral( "updated" ) ) { auto data = args[1].toObject(); int unread = data[QStringLiteral( "unread" )].toInt(); QString rid; if ( data.contains( "rid" ) ) { rid = data[QStringLiteral( "rid" )].toString(); } else if ( data.contains( QStringLiteral( "_id" ) ) ) { rid = data[QStringLiteral( "_id" )].toString(); } else { qWarning() << "invalid channel update message"; return ; } qint64 updatedAt = -1; if ( data.contains( "_updatedAt" ) ) { auto updatedObj = data["_updatedAt"].toObject(); updatedAt = static_cast( updatedObj["$date"].toDouble() ); } auto type = data[QStringLiteral( "t" )].toString(); bool blocked = false; auto name = data[QStringLiteral( "name" )].toString(); QString username = ""; bool readOnly = false; if ( data.contains( QStringLiteral( "ro" ) ) ) { readOnly = data[QStringLiteral( "ro" )].toBool(); } if ( type == "d" ) { username = name; name = data[QStringLiteral( "fname" )].toString(); } bool hidden = false; if ( data.contains( QStringLiteral( "open" ) ) ) { hidden = !data[QStringLiteral( "open" )].toBool(); } if ( data.contains( QStringLiteral( "blocker" ) ) ) { blocked = data[QStringLiteral( "blocker" )].toBool(); //TODO: clean this up!, there should be a user service QString otherUserId = rid; otherUserId = otherUserId.replace( mUserId, "" ); if ( data.contains( "name" ) ) { QString otherUserName = data["name"].toString(); mStorage->addUserToBlockList( otherUserName, otherUserId ); mMessageService->addUserToBlockList( otherUserId ); leaveChannel( rid ); } } if ( hidden ) { mChannelService->deleteChannel( rid ); } else { QSharedPointer channel; bool newObj = false; if ( mChannels->contains( rid ) && !mChannels->get( rid ).isNull() ) { channel = mChannels->get( rid ); } else { channel = mChannelService->createChannelObject( rid, name, type, username ); newObj = true; } if ( name != "" ) { channel->setName( name ); } if ( updatedAt != -1 ) { channel->setUpdatedAt( updatedAt ); } channel->setReadOnly( readOnly ); channel->setType( type ); channel->setBlocked( blocked ); channel->setUnreadMessages( static_cast( unread ) ); channel->setUsername( username ); if ( newObj ) { mChannels->add( rid, channel ); } } } } } } uint RocketChatServerData::diffToLastDDPPing() { return mDdpApi->diffToLastMessage(); } void RocketChatServerData::getFileRessource( const QString &pUrl ) { getFileRessource( pUrl, QStringLiteral( "temp" ) ); } void RocketChatServerData::getFileRessource( const QString &pUrl, const QString &pType ) { auto showInline = false; if ( pType == QStringLiteral( "temp" ) ) { showInline = true; } auto then = [ = ]( QSharedPointer tempfile, bool showInline ) { emit( fileRessourceProcessed( pUrl, tempfile->getFilePath(), showInline ) ); }; auto request = QSharedPointer::create( pUrl, pType, then, showInline ); mFileService->getFileRessource( request ); } void RocketChatServerData::sendPushToken( const QString &pToken ) { if ( !mUserId.isEmpty() && !pToken.isEmpty() ) { qDebug() << "send push token " + pToken; auto sendToken = QSharedPointer::create( pToken, mUserId ); sendDdprequest( sendToken ); } } void RocketChatServerData::sendUnsentMessages() { qDebug() << "send unsend ddp messages"; if ( mConnectionState == ConnectionState::ONLINE ) { for ( const auto &request : mUnsendDdpRequests ) { if ( !request.isNull() ) { sendDdprequest( request ); } } mUnsendDdpRequests.clear(); qDebug() << "send unsend rest messages"; for ( const auto &request : mUnsendRestRequests ) { if ( !request.isNull() ) { sendApiRequest( request ); } } mUnsendRestRequests.clear(); qDebug() << "all messages send"; } else if ( mConnectionState == ConnectionState::CONNECTED ) { for ( const auto &request : mUnsendDdpRequestsOnConnected ) { if ( !request.isNull() ) { sendDdprequest( request, true, true ); } } mUnsendDdpRequestsOnConnected.clear(); qDebug() << "send unsend rest messages"; } } void RocketChatServerData::sendApiRequest( const RestApiRequest &pRequest, bool pRetry ) { RestRequestCallback originalCallback = pRequest->getError(); if ( pRetry ) { RestRequestCallback errorWrapperFunction = [ = ]( QNetworkReply * pReply, QJsonObject pData, RestApi * pRestApi ) { if ( pReply && pRestApi ) { mUnsendRestRequests.append( pRequest ); if ( originalCallback ) { originalCallback( pReply, pData, pRestApi ); } } }; pRequest->setError( errorWrapperFunction ); } mRestApi->sendRequest( pRequest ); } void RocketChatServerData::sendDdprequest( const QSharedPointer &pRequest, bool pRetry, bool pSendWhenConnected ) { if ( !pRequest.isNull() ) { uint diff = diffToLastDDPPing(); qDebug( "%d diff to last ping %d", static_cast( mConnectionState ), diff ); bool intime = diff <= 29; if ( Q_LIKELY( mConnectionState == ConnectionState::ONLINE && intime ) ) { mDdpApi->sendRequest( pRequest ); } else if ( mConnectionState == ConnectionState::CONNECTED && intime && pSendWhenConnected ) { mDdpApi->sendRequest( pRequest ); } else if ( pRetry && pSendWhenConnected ) { mUnsendDdpRequestsOnConnected.append( pRequest ); } else if ( pRetry ) { qDebug() << "message queued"; mUnsendDdpRequests.append( pRequest ); } } } void RocketChatServerData::onDDPMessageReceived( const QJsonObject &pMessage ) { sendUnsentMessages(); if ( Q_LIKELY( pMessage.contains( QStringLiteral( "collection" ) ) ) ) { if ( pMessage[QStringLiteral( "collection" )] == QStringLiteral( "stream-notify-user" ) ) { handleChannelMessage( pMessage ); } else if ( pMessage[QStringLiteral( "collection" )] == QStringLiteral( "users" ) ) { QString userId = pMessage[QStringLiteral( "id" )].toString(); QString msg = pMessage[QStringLiteral( "msg" )].toString(); QJsonObject fields = pMessage["fields"].toObject(); //race condition regarding user insertion into model, fixed for now with mutex and merge in model auto user = userModel->getUserById( userId ); if ( !user.isNull() ) { if ( msg == QStringLiteral( "added" ) || msg == QStringLiteral( "changed" ) ) { if ( fields.contains( QStringLiteral( "statusDefault" ) ) && userId == mUserId ) { QString msgStatus = fields[QStringLiteral( "statusDefault" )].toString(); user->setStatus( msgStatus ); } else if ( fields.contains( QStringLiteral( "status" ) ) ) { QString msgStatus = fields[QStringLiteral( "status" )].toString(); user->setStatus( msgStatus ); } if ( fields.contains( QStringLiteral( "name" ) ) ) { user->setName( fields[QStringLiteral( "name" )].toString() ); } if ( fields.contains( QStringLiteral( "username" ) ) ) { user->setUserName( fields[QStringLiteral( "username" )].toString() ); } } else if ( msg == QStringLiteral( "removed" ) ) { //TODO: delete user user->setStatus( RocketChatUser::status::OFFLINE ); } } else { auto newUser = QSharedPointer::create( userId ); if ( pMessage.contains( "fields" ) ) { QJsonObject fields = pMessage["fields"].toObject(); if ( fields.contains( QStringLiteral( "statusDefault" ) ) && userId == mUserId ) { QString msgStatus = fields[QStringLiteral( "statusDefault" )].toString(); newUser->setStatus( msgStatus ); } else if ( fields.contains( QStringLiteral( "status" ) ) && userId != mUserId ) { QString msgStatus = fields[QStringLiteral( "status" )].toString(); newUser->setStatus( msgStatus ); } if ( fields.contains( QStringLiteral( "name" ) ) ) { newUser->setName( fields[QStringLiteral( "name" )].toString() ); } if ( fields.contains( QStringLiteral( "username" ) ) ) { newUser->setUserName( fields[QStringLiteral( "username" )].toString() ); } } auto result = QMetaObject::invokeMethod( userModel, "addUser", Q_ARG( User, newUser ) ); Q_ASSERT( result ); } } else if ( pMessage[QStringLiteral( "collection" )] == QStringLiteral( "autocompleteRecords" ) ) { // not used anymore } else if ( pMessage[QStringLiteral( "collection" )] == QStringLiteral( "meteor_accounts_loginServiceConfiguration" ) ) { if ( Q_LIKELY( pMessage.contains( QStringLiteral( "fields" ) ) && pMessage.contains( QStringLiteral( "msg" ) ) && pMessage.contains( QStringLiteral( "id" ) ) ) ) { QString msg = pMessage[QStringLiteral( "msg" )].toString(); if ( msg == QStringLiteral( "added" ) ) { QJsonObject data = pMessage[QStringLiteral( "fields" )].toObject(); if ( data.contains( QStringLiteral( "scope" ) ) ) { QString scope = data[QStringLiteral( "scope" )].toString(); if ( scope == QStringLiteral( "openid" ) ) { // QMap entry = data.toVariantMap(); QString service = data[QStringLiteral( "service" )].toString(); mLoginMethodRepo.addLoginMethod( data, QStringLiteral( "https://" ) + mBaseUrl ); if ( mLoginMethodRepo.contains( service ) ) { LoginMethod loginMethod = mLoginMethodRepo.get( service ); emit newLoginMethod( loginMethod ); } } } } } } else if ( isUserJoinMessage( pMessage ) ) { handleUserJoinMessage( pMessage ); } else if ( Q_LIKELY( isStreamRoomMessage( pMessage ) ) ) { handleStreamRoomMessage( pMessage ); } else if ( pMessage[QStringLiteral( "collection" )] == QStringLiteral( "stream-notify-room" ) ) { QJsonObject fields = pMessage["fields"].toObject(); if ( fields.contains( "eventName" ) ) { QString eventname = fields["eventName"].toString(); if ( eventname.contains( "deleteMessage" ) ) { QStringList tokens = eventname.split( "/" ); if ( tokens.size() == 2 ) { QString roomId = tokens[0]; if ( fields.contains( "args" ) && fields["args"].isArray() ) { QJsonArray args = fields["args"].toArray(); QJsonObject idarg; if ( args.size() && args[0].isObject() && ( idarg = args[0].toObject() ).contains( "_id" ) ) { QString messageId = idarg["_id"].toString(); if ( mChannels->contains( roomId ) ) { auto room = mChannels->get( roomId ); room->deleteMessage( messageId ); mStorage->deleteMessage( roomId ); } } } } } } } } } bool RocketChatServerData::isStreamRoomMessage( const QJsonObject &pMessage ) const { return Q_LIKELY( pMessage.contains( QStringLiteral( "collection" ) ) && pMessage[QStringLiteral( "collection" )] == QStringLiteral( "stream-room-messages" ) ); } bool RocketChatServerData::isUserJoinMessage( const QJsonObject &pMessage ) const { return pMessage.contains( QStringLiteral( "fields" ) ) && pMessage[QStringLiteral( "fields" )].toObject().contains( "t" ) && pMessage[QStringLiteral( "fields" )].toObject()["t"] == QStringLiteral( "uj" ); } void RocketChatServerData::handleStreamRoomMessage( const QJsonObject &pMessage ) { if ( Q_LIKELY( pMessage.contains( QStringLiteral( "fields" ) ) ) ) { auto fields = pMessage[QStringLiteral( "fields" )].toObject(); if ( Q_LIKELY( fields.contains( QStringLiteral( "args" ) ) ) ) { auto args = fields[QStringLiteral( "args" )].toArray(); for ( const auto currentArg : args ) { auto currentArgObj = currentArg.toObject(); if ( Q_LIKELY( currentArgObj.contains( QStringLiteral( "rid" ) ) && currentArgObj.contains( QStringLiteral( "_id" ) ) ) ) { auto roomId = currentArgObj[QStringLiteral( "rid" )].toString(); auto message = mMessageService->parseMessage( currentArgObj, true ); if ( Q_LIKELY( mChannels->contains( roomId ) ) ) { auto channel = mChannels->get( roomId ); mChannels->get( roomId )->addMessage( message ); mMessageService->persistMessage( message ); if ( message->getMessageType() == "au" ) { mChannelService->loadUsersOfChannel( channel ); } } } } } } } void RocketChatServerData::handleUserJoinMessage( const QJsonObject &pMessage ) { if ( Q_LIKELY( pMessage.contains( QStringLiteral( "fields" ) ) ) ) { QJsonObject fields = pMessage[QStringLiteral( "fields" )].toObject(); if ( Q_LIKELY( fields.contains( "rid" ) && fields.contains( "u" ) ) ) { QString roomId = fields[QStringLiteral( "rid" )].toString(); QJsonObject userData = fields["u"].toObject(); if ( Q_LIKELY( userData.contains( QStringLiteral( "_id" ) ) && userData.contains( QStringLiteral( "username" ) ) ) ) { if ( Q_LIKELY( mChannels->contains( roomId ) ) ) { auto channel = mChannels->get( roomId ); if ( !channel.isNull() ) { /* QSharedPointer ptr( new RocketChatUser( username ) ); ptr->setUserId( userId ); */ mChannelService->loadUsersOfChannel( channel ); } } } } } } void RocketChatServerData::onLoginError() { qDebug() << "login error"; //if there is a login error reset anything mConnectionState = ConnectionState::CONNECTED; emit loginError(); } void RocketChatServerData::onResume() { qDebug() << "resubscribing channel"; std::tuple channelData = mStorage->getCurrentChannel(); QString channelId = std::get<0>( channelData ); sendUnsentMessages(); if ( mChannels->contains( channelId ) ) { auto channel = mChannels->get( channelId ); mChannelService->subscribeChannel( channel ); //loadMissedMessages(); qDebug() << "connection resumed"; } if ( !mCurrentChannel.isEmpty() && mChannels->contains( mCurrentChannel ) ) { mChannels->get( mCurrentChannel )->setUnreadMessages( 0 ); } } void RocketChatServerData::joinJitsiCall( const QString &pRoomId ) { if ( Q_LIKELY( mChannels->contains( pRoomId ) ) ) { QString hash; bool defaultHash = true; int major = std::get<0>( RocketChatServerConfig::serverVersion ); int minor = std::get<1>( RocketChatServerConfig::serverVersion ); int patch = std::get<2>( RocketChatServerConfig::serverVersion ); if ( major != -1 && minor != -1 && patch != -1 ) { if ( major >= 0 && minor > 60 ) { hash = "undefined" + pRoomId; defaultHash = false; } } if ( defaultHash ) { hash = QCryptographicHash::hash( ( RocketChatServerConfig::uniqueId + pRoomId ).toUtf8(), QCryptographicHash::Md5 ).toHex(); } #if defined(Q_OS_IOS) || defined(Q_OS_ANDROID) QString scheme = QStringLiteral( "org.jitsi.meet://" ); #else QString scheme = "https://"; #endif QString url = scheme + RocketChatServerConfig::jitsiMeetUrl + '/' + RocketChatServerConfig::jitsiMeetPrefix + hash; qDebug() << url; emit openUrl( url ); } } void RocketChatServerData::setUnreadMessages( const QString &pChannelId, int count ) { Q_UNUSED( count ) if ( mChannels->contains( pChannelId ) ) { auto channel = mChannels->get( pChannelId ); channel->setUnreadMessages( 0 ); } } ConnectionState RocketChatServerData::getConnectionState() const { return mConnectionState; } void RocketChatServerData::setConnectionState( const ConnectionState &pValue ) { mConnectionState = pValue; } QString RocketChatServerData::getCurrentChannel() const { return mCurrentChannel; } void RocketChatServerData::setCurrentChannel( const QString &pCurrentChannel ) { if ( mChannels->contains( pCurrentChannel ) && !mChannels->get( pCurrentChannel ).isNull() ) { mCurrentChannel = pCurrentChannel; auto channel = mChannels->get( pCurrentChannel ); mStorage->setCurrentChannel( pCurrentChannel, channel->getName() ); } else if ( pCurrentChannel == "" ) { mCurrentChannel = pCurrentChannel; mStorage->setCurrentChannel( "", "" ); } } QString RocketChatServerData::getUsername() const { return mUsername; } void RocketChatServerData::setUsername( const QString &pUsername ) { mUsername = pUsername; mStorage->setUserName( pUsername ); } void RocketChatServerData::getServerSettings() { QJsonArray params = {}; auto request = QSharedPointer::create( QStringLiteral( "public-settings/get" ), params ); DdpCallback sucess = [ = ]( QJsonObject data, MeteorDDP * ddpApi ) { Q_UNUSED( ddpApi ); QJsonArray configs = data[QStringLiteral( "result" )].toArray(); for ( const auto currentConfig : configs ) { QJsonObject currentConfObject = currentConfig.toObject(); QString id = currentConfObject[QStringLiteral( "_id" )].toString(); QString value = currentConfObject[QStringLiteral( "value" )].toString(); if ( id == QStringLiteral( "uniqueID" ) ) { RocketChatServerConfig::uniqueId = value; } if ( id == QStringLiteral( "Jitsi_Domain" ) ) { RocketChatServerConfig::jitsiMeetUrl = value; } if ( id == QStringLiteral( "Jitsi_URL_Room_Prefix" ) ) { RocketChatServerConfig::jitsiMeetPrefix = value; } } qDebug() << "processed config"; }; request->setSuccess( sucess ); sendDdprequest( request, true, true ); } void RocketChatServerData::onChannelsLoaded( const QVector> &pChannels ) { Q_UNUSED( pChannels ); qDebug() << "on channels loaded "; loadHistories(); } void RocketChatServerData::onLoggedIn() { mLoggedIn = true; mChannelService->loadJoinedChannelsFromServer(); emit loggedIn( mServerId ); if ( !mPendingSwitchRoomRequest.isEmpty() && !mPendingSwitchRoomRequestType.isEmpty() ) { switchChannelByName( mPendingSwitchRoomRequest, mPendingSwitchRoomRequestType ); } } void RocketChatServerData::markChannelAsRead( const QString &pChannelId ) { if ( Q_LIKELY( mChannels->contains( pChannelId ) ) ) { auto markAsRead = QSharedPointer::create( pChannelId ); sendDdprequest( markAsRead ); } } void RocketChatServerData::joinChannel( const QString &pChannelId, bool pForce ) { Q_UNUSED( pForce ) if ( Q_LIKELY( mChannels->contains( pChannelId ) ) ) { // if(mCurrentChannel != pChannelId){ mCurrentChannel = pChannelId; auto channel = mChannels->get( pChannelId ); mChannelService->subscribeChannel( channel ); setUnreadMessages( pChannelId, 0 ); markChannelAsRead( pChannelId ); setCurrentChannel( pChannelId ); auto serviceRequest = QSharedPointer::create( pChannelId ); serviceRequest->setSuccess( historyLoaded ); serviceRequest->setLimit( 300 ); mMessageService->loadHistory( serviceRequest ); // } } else { //TODO: change /* DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * pDdpApi ) { Q_UNUSED( pDdpApi ); if(pResponse.contains("result")){ } }; QSharedPointer request(new RocketChatGetRoomById(pChannelId)); request->setSuccess(success); sendDdprequest(request);*/ } // qCritical() << "Subscribing channel that is not existent."; } void RocketChatServerData::joinChannelByNameAndType( const QString &pChannelName, const QString &pType ) { DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * pDdpApi ) { Q_UNUSED( pDdpApi ); if ( pResponse.contains( "result" ) ) { auto result = pResponse["result"].toObject(); if ( result.contains( "_id" ) && result.contains( "ro" ) && result.contains( "t" ) && result.contains( "name" ) ) { QString rid = result["_id"].toString(); bool ro = result["ro"].toBool(); QString type = result["t"].toString(); QString name = result["name"].toString(); QSharedPointer channel = nullptr; if ( !mChannels->contains( rid ) ) { channel = mChannelService->createChannelObject( rid, name, type ); channel->setReadOnly( ro ); mChannels->add( rid, channel ); } else { channel = mChannels->get( rid ); } joinChannel( rid ); } } }; if ( pType == "d" ) { mChannelService->openPrivateChannelWith( pChannelName ); } else { auto request = QSharedPointer::create( pChannelName, pType ); request->setSuccess( success ); sendDdprequest( request ); } } QString RocketChatServerData::getServerId() const { return mServerId; } void RocketChatServerData::setServerId( const QString &pValue ) { mServerId = pValue; } bool RocketChatServerData::isConnected() const { return mConnected; } bool RocketChatServerData::isWebsocketValid() const { return mDdpApi->websocketIsValid(); } void RocketChatServerData::loadRecentHistory( const QString &pChannelId ) { qDebug() << "load recent history called"; if ( Q_LIKELY( mChannels->contains( pChannelId ) ) ) { auto channel = mChannels->get( pChannelId ); auto oldestMessage = channel->getOldestMessage(); double end = QDateTime::currentMSecsSinceEpoch(); if ( !oldestMessage.isNull() ) { end = oldestMessage->getTimestamp(); } double start = end - ( 86400 * 3 * 1000 ); auto request = QSharedPointer::create( pChannelId ); request->setNoLastThreeDays( true ); request->setStart( start ); request->setEnd( end ); std::function > *messages )> success = [ = ]( QMultiMap > *messages ) { if ( messages ) { auto messageList = messages->values( pChannelId ); qDebug() << "load recent history successfull loaded " << messageList.size(); mChannels->get( pChannelId )->addMessages( messageList ); mMessageService->persistMessages( messageList ); delete messages; } }; request->setSuccess( success ); mMessageService->loadHistory( request ); } } void RocketChatServerData::sendMessage( const QString &pMessage, const QString &pChannelId ) { auto message = QSharedPointer< RocketChatMessage>::create( pChannelId, pMessage ); mMessageService->sendMessage( message ); } void RocketChatServerData::getCustomEmojis() { std::function> )> success = [ = ]( const QList> &pEmojiList ) { if ( !pEmojiList.isEmpty() ) { auto emojiModel = Models::getEmojisModel(); mStorage->transaction(); QList> newEmojis; for ( auto &emoji : pEmojiList ) { if ( !emoji.isNull() ) { if ( !mEmojiRepo->contains( emoji->getIdentifier() ) ) { mEmojiService->persistEmoji( emoji ); mEmojiRepo->add( emoji->getIdentifier(), emoji ); newEmojis.append( std::move( emoji ) ); } } } if ( !newEmojis.isEmpty() ) { auto result = QMetaObject::invokeMethod( emojiModel, "addEmojisByCategory", Q_ARG( QString, "custom" ), Q_ARG( QList>, newEmojis ) ); Q_ASSERT( result ); } mStorage->askForcommit(); } if ( !mCustomEmojisReady ) { onLoggedIn(); qDebug() << "ddp authenticated"; this->initialised = true; mCustomEmojisReady = true; } }; auto notifyRoomsChanged = QSharedPointer::create( this->mUserId ); auto notifySubSubscription = QSharedPointer::create( this->mUserId ); auto notifyNoticeSubSubscription = QSharedPointer::create( this->mUserId ); auto noitfySubscriptionsSubscription = QSharedPointer::create( this->mUserId ); auto activeUsersSubscription = QSharedPointer::create(); auto userData = QSharedPointer::create(); sendDdprequest( notifySubSubscription, true ); sendDdprequest( notifyNoticeSubSubscription, true ); sendDdprequest( noitfySubscriptionsSubscription, true ); sendDdprequest( activeUsersSubscription, true ); sendDdprequest( notifyRoomsChanged, true ); sendDdprequest( userData, true ); sendUnsentMessages(); getServerSettings(); emit registerForPush(); if ( mCustomEmojisReady ) { onLoggedIn(); qDebug() << "ddp authenticated"; this->initialised = true; } mEmojiService->loadCustomEmojis( std::move( success ) ); } void RocketChatServerData::openPrivateChannelWith( const QString &pUsername ) { if ( mChannels->getChannelByName( pUsername ) ) { QSharedPointer channel = mChannels->getChannelByName( pUsername ); switchChannelByName( channel->getUsername(), channel->getType() ); } else { mChannelService->openPrivateChannelWith( pUsername ); } } //TODO: clear up memory when finished with unsetbinding void RocketChatServerData::uploadFile( const QString &pChannelId, const QString &pFilePath ) { qDebug() << "uploading file: " << pFilePath; qDebug() << "channelId: " << pChannelId; if ( !pFilePath.isEmpty() && !pChannelId.isEmpty() ) { QUrl uri( pFilePath ); QFile file( pFilePath ); if ( file.open( QFile::ReadOnly ) ) { file.close(); } auto uploader = new FileUploader( this, this, uri, pChannelId ); std::function success = [ = ]() { emit fileUploadFinished( uploader->getFileId() ); delete uploader; }; connect( uploader, &FileUploader::ufsCreated, this, [ = ]( const QString & fileId ) { QJsonObject data; data[QStringLiteral( "fileId" )] = fileId; data[QStringLiteral( "filePath" )] = Utils::getPathPrefix() + pFilePath; data[QStringLiteral( "filename" )] = uri.fileName(); mFileUploads[fileId] = uploader; QJsonDocument doc( data ); emit fileuploadStarted( doc.toJson() ); } ); connect( uploader, &FileUploader::progressChanged, this, [ = ]( double progress ) { qDebug() << "progress changed"; QJsonObject data; data[QStringLiteral( "fileId" )] = uploader->getFileId(); data[QStringLiteral( "progress" )] = progress; QJsonDocument doc( data ); emit fileUploadProgressChanged( doc.toJson() ); } ); connect( uploader, &FileUploader::error, this, [ = ]( const QString & errorString ) { emit error( errorString ); } ); try { uploader->upload( success ); } catch ( std::exception &e ) { qCritical() << e.what(); } } } void RocketChatServerData::cancelUpload( const QString &pFileId ) { if ( mFileUploads.contains( pFileId ) ) { auto upload = mFileUploads[pFileId]; upload->cancel(); mFileUploads.remove( pFileId ); } } void RocketChatServerData::getLoginMethods() { DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * pDdpApi ) { Q_UNUSED( pResponse ) Q_UNUSED( pDdpApi ); qDebug() << "loginMethods Recieved"; }; auto loginMethodsSubscription = QSharedPointer::create(); loginMethodsSubscription->setSuccess( success ); // sendDdprequest( loginMethodsSubscription ); mDdpApi->sendRequest( loginMethodsSubscription ); } void RocketChatServerData::getServerInfo() { RestRequestCallback success = [ = ]( QNetworkReply * pReply, QJsonObject data, RestApi * pApi ) { Q_UNUSED( pApi ); Q_UNUSED( pReply ); qDebug() << data; if ( data.contains( QStringLiteral( "info" ) ) ) { QJsonObject info = data[QStringLiteral( "info" )].toObject(); if ( info.contains( QStringLiteral( "version" ) ) ) { QString version = info[QStringLiteral( "version" )].toString(); QStringList verParts = version.split( '.' ); std::get<0>( RocketChatServerConfig::serverVersion ) = verParts[0].toInt(); std::get<1>( RocketChatServerConfig::serverVersion ) = verParts[1].toInt(); std::get<2>( RocketChatServerConfig::serverVersion ) = verParts[2].toInt(); mStorage->setSetting( "serverversion", version ); } } }; auto serverInfoRequest = QSharedPointer::create( success, nullptr ); mRestApi->sendRequest( serverInfoRequest ); } void RocketChatServerData::leaveChannel( const QString &pId ) { mChannelService->leaveChannel( pId ); } void RocketChatServerData::hideChannel( const QString &pId ) { mChannelService->hideRoom( pId ); } void RocketChatServerData::reportAbusiveContent( const QString &pMessageId, const QString &pAuthor ) { mChannelService->reportContent( pMessageId, pAuthor ); } void RocketChatServerData::searchMessage( const QString &pTerm, const QString &pRoom ) { mMessageService->searchMessage( pTerm, pRoom ); } void RocketChatServerData::searchRoom( const QString &pTerm, const QString &pType ) { mChannelService->searchRoom( pTerm, pType ); } void RocketChatServerData::requestNewVideoPath() { emit videoPath( mStorage->getNewVideoPath() ); } void RocketChatServerData::setSetting( const QString &pKey, const QString &pValue ) { mStorage->setSetting( pKey, pValue ); } void RocketChatServerData::getSetting( const QString &pKey ) { auto value = mStorage->getSetting( pKey ); emit setting( pKey, value ); } RestApi *RocketChatServerData::getRestApi() const { return mRestApi; } void RocketChatServerData::resume( ) { mResumeOperation = true; mDdpApi->resume(); } void RocketChatServerData::logout() { qDebug() << "logged out"; auto logoutRequest = QSharedPointer::create(); mResumeOperation = false; emit resetLoginMethods(); mDdpApi->resume(); // mRestApi->logout(); mRestApi->sendRequest( logoutRequest ); mChannels->clear(); mUsers.clear(); mLoginMethodRepo.clear(); mStorage->wipeDb(); this->mUsername = ""; mDdpApi->setToken( "" ); // mRestApi->deleteLater(); // setRestApi( new RestApi( "https://" + mBaseUrl, mApiUri ) ); // mRestApi->moveToThread(QThread::currentThread()); mLoggedIn = false; emit loggedOut( this->mServerId ); } void RocketChatServerData::createPublicGroup( const QString &pChannelName, const QStringList &pUsers, bool pReadonly ) { DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * pDdpApi ) { Q_UNUSED( pResponse ); Q_UNUSED( pDdpApi ); qDebug() << "created channel"; if ( pResponse.contains( "result" ) ) { QJsonObject result = pResponse["result"].toObject(); if ( result.contains( "rid" ) && result.contains( "name" ) ) { QString rid = result["rid"].toString(); QString name = result["name"].toString(); switchChannel( "default", rid, name, "c" ); } } }; DdpCallback errorFunc = [ = ]( QJsonObject object, MeteorDDP * ) { QString reason; QJsonObject errorObject = object["error"].toObject(); if ( errorObject.contains( "reason" ) ) { if ( errorObject.contains( "reason" ) ) { reason = errorObject["reason"].toString(); } } emit error( tr( "Room creation failed. " ) + reason ); return; }; auto request = QSharedPointer::create( pChannelName, pUsers, pReadonly, success ); request->setError( errorFunc ); sendDdprequest( request ); } void RocketChatServerData::createPrivateGroup( const QString &pChannelName, const QStringList &pUsers, bool pReadonly ) { DdpCallback success = [ = ]( QJsonObject pResponse, MeteorDDP * ddpApi ) { Q_UNUSED( ddpApi ); qDebug() << "created group"; if ( pResponse.contains( "result" ) ) { QJsonObject result = pResponse["result"].toObject(); if ( result.contains( "rid" ) && result.contains( "name" ) ) { QString rid = result["rid"].toString(); QString name = result["name"].toString(); switchChannel( "default", rid, name, "p" ); } } }; auto request = QSharedPointer::create( pChannelName, pUsers, pReadonly ); request->setSuccess( success ); sendDdprequest( request ); } void RocketChatServerData::addUsersToChannel( const QString &pChannelId, const QStringList &pUsers ) { for ( const auto &user : pUsers ) { DdpCallback success = [ = ]( QJsonObject response, MeteorDDP * ddpApi ) { Q_UNUSED( response ); Q_UNUSED( ddpApi ); qDebug() << "added users to channel"; }; if ( Q_LIKELY( !user.isEmpty() ) ) { QStringList usernames( user ); auto request = QSharedPointer::create( pChannelId, usernames ); request->setSuccess( success ); sendDdprequest( request ); } } } ChannelRepository *RocketChatServerData::getChannels() const { return mChannels; } FilesRepo *RocketChatServerData::getFiles() const { return mFilesRepo; }