diff --git a/src/server/Database.cpp b/src/server/Database.cpp index 69b7651af..cf5070aba 100644 --- a/src/server/Database.cpp +++ b/src/server/Database.cpp @@ -1,124 +1,168 @@ /* GCompris - Database.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 #include #include #include #include #include #include #include "UserData.h" #include "Database.h" +#include "GroupData.h" -#define CREATE_TABLE_USERS \ - "CREATE TABLE users (user_id INT UNIQUE, login TEXT, avatar TEXT); " -#define CREATE_TABLE_GROUPS \ - "CREATE TABLE groups (group_id INT UNIQUE, name TEXT, description TEXT, userId INT); " +#define CREATE_TABLE_GROUPS \ + "CREATE TABLE groups (group_name TEXT PRIMARY KEY NOT NULL, description TEXT); " Database* Database::_instance = 0; Database::Database() { } Database::~Database() { } Database* Database::getInstance() { if (!_instance) _instance = new Database; return _instance; } -bool Database::addUser(const QString &name, const QString &avatar) +bool Database::addGroup(const QString &groupName, const QString& description, + const QStringList& users) { - QSqlDatabase dbConnection = QSqlDatabase::database(); - // Don't add twice the same login - QSqlQuery alreadyExistingUser(dbConnection); - alreadyExistingUser.prepare("SELECT * FROM users WHERE login=:name"); - alreadyExistingUser.bindValue(":name", name); - alreadyExistingUser.exec(); - if(alreadyExistingUser.next()) { - qDebug() << "User " << name << " already exists"; + bool groupAdded = false; + QSqlDatabase dbConnection = QSqlDatabase::database(); + QSqlQuery query(dbConnection); + // add group to db only if it has not been added before + query.prepare("SELECT group_name FROM groups WHERE group_name=:groupName"); + query.bindValue(":groupName", groupName); + query.exec(); + if(query.next()){ + qDebug()<< "group "<< groupName << " already exists"; return false; } + // since the group does not exist ,create the new group and add description and users to it + query.prepare("INSERT INTO groups (group_name, description) VALUES (:groupName,:description)"); + query.bindValue(":groupName", groupName); + query.bindValue(":description",description); + groupAdded = query.exec(); + if(groupAdded){ + //add users to the group + } + else{ - // Add the user in the database + qDebug()<<"group could not be added " << query.lastError(); + } + return groupAdded; +} +bool Database::deleteGroup(const QString &groupName) +{ + bool groupDeleted = false; + QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); - query.prepare("INSERT INTO users (login, avatar) VALUES (:name, :avatar)"); - query.bindValue(":name", name); - query.bindValue(":avatar", avatar); - bool returnValue = query.exec(); - if(!returnValue) - qDebug() << query.lastError(); - return returnValue; + query.prepare("DELETE FROM groups WHERE group_name=:gname"); + query.bindValue(":gname",groupName); + if(query.exec()){ + groupDeleted = true; + // query.prepare("DELETE FROM group_users WHERE group_name=:gname"); + // query.bindValue(":gname",groupName); + // if(query.exec()) + // groupDeleted = true; + } + return groupDeleted; + } +void Database::retrieveAllExistingGroups(QList &allGroups) +{ + QSqlDatabase dbConnection = QSqlDatabase::database(); + + // Don't add twice the same login + QSqlQuery query(dbConnection); + query.prepare("SELECT * FROM groups"); + query.exec(); + const int nameIndex = query.record().indexOf("group_name"); + const int descriptionIndex = query.record().indexOf("description"); + while(query.next()) { + GroupData *g = new GroupData(); + g->setName(query.value(nameIndex).toString()); + g->setDescription(query.value(descriptionIndex).toString()); + allGroups.push_back(g); + } +} + + void Database::retrieveAllExistingUsers(QList &allUsers) { QSqlDatabase dbConnection = QSqlDatabase::database(); // Don't add twice the same login QSqlQuery query(dbConnection); query.prepare("SELECT * FROM users"); query.exec(); const int nameIndex = query.record().indexOf("login"); const int avatarIndex = query.record().indexOf("avatar"); while(query.next()) { UserData *u = new UserData(); u->setName(query.value(nameIndex).toString()); u->setAvatar(query.value(avatarIndex).toString()); allUsers.push_back(u); } } void createDatabase(const QString &path) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); - query.exec(CREATE_TABLE_USERS); - query.exec(CREATE_TABLE_GROUPS); + if(query.exec(CREATE_TABLE_GROUPS)) + qDebug()<< "created table groups"; + else{ + qDebug() <<"failed"; + qDebug() << query.lastError(); + } } void Database::init() { QDir databasePath; QString path = databasePath.currentPath()+"/gcompris-qt.db"; // todo set cache/data path instead of current folder QSqlDatabase dbConnection = QSqlDatabase::addDatabase("QSQLITE"); dbConnection.setDatabaseName(path); QFileInfo fileInfo(path); if(!fileInfo.exists()) { if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } createDatabase(path); } if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } } diff --git a/src/server/Database.h b/src/server/Database.h index e45f7c8b0..e3d6db456 100644 --- a/src/server/Database.h +++ b/src/server/Database.h @@ -1,52 +1,57 @@ /* GCompris - Database.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 DATABASE_H #define DATABASE_H #include #include class UserData; +class GroupData; class QSqlError; class Database : public QObject { Q_OBJECT private: Database(); static Database* _instance; // singleton instance public: /** * Registers Database singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static Database* getInstance(); ~Database(); - bool addUser(const QString &name, const QString &avatar = ""); + bool addGroup(const QString &groupName, const QString& description = QString(), + const QStringList& users=QStringList()); + bool deleteGroup(const QString& groupName); void retrieveAllExistingUsers(QList &allUsers); + void retrieveAllExistingGroups(QList &allGroups); + }; #endif diff --git a/src/server/GroupData.cpp b/src/server/GroupData.cpp index fcb6df08a..4e4abaf92 100644 --- a/src/server/GroupData.cpp +++ b/src/server/GroupData.cpp @@ -1,79 +1,85 @@ /* GCompris - GroupData.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 #include #include "UserData.h" #include "GroupData.h" #include "MessageHandler.h" // not good having it here? GroupData::GroupData() { } GroupData::GroupData(const GroupData &group) { m_users = group.m_users; m_name = group.m_name; } GroupData::~GroupData() { } const QList &GroupData::getUsers() const { return m_users; } void GroupData::addUser(UserData *user) { - m_users << user; + if(!m_users.contains(user)) + m_users << user; emit newUsers(); } void GroupData::removeUser(UserData *user) { m_users.removeAll(user); emit newUsers(); } void GroupData::removeAllUsers() { m_users.clear(); emit newUsers(); } bool GroupData::hasUser(const QString &userName) { UserData *user = MessageHandler::getInstance()->getUser(userName); return m_users.contains(user); } const QString &GroupData::getName() const { return m_name; } +void GroupData::setDescription(const QString &description) +{ + m_description = description; +} + void GroupData::setName(const QString &name) { m_name = name; emit newName(); } diff --git a/src/server/GroupData.h b/src/server/GroupData.h index 7f0595ea2..b5cddf661 100644 --- a/src/server/GroupData.h +++ b/src/server/GroupData.h @@ -1,69 +1,70 @@ /* GCompris - GroupData.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 GROUPDATA_H #define GROUPDATA_H #include #include class UserData; /** * @class GroupData * @short Contains all the data relative to a group * * A group is composed of a list of users and has a name identifier. * * @sa UserData */ class GroupData : public QObject { Q_OBJECT Q_PROPERTY(QList users MEMBER m_users NOTIFY newUsers) Q_PROPERTY(QString name MEMBER m_name NOTIFY newName) public: GroupData(); GroupData(const GroupData &group); ~GroupData(); const QList &getUsers() const; void addUser(UserData *user); void removeUser(UserData *user); void removeAllUsers(); Q_INVOKABLE bool hasUser(const QString &user); const QString &getName() const; void setName(const QString &newName); - - private: + void setDescription(const QString &description); +private: // UserData* QList m_users; QString m_name; + QString m_description; signals: void newUsers(); void newName(); }; Q_DECLARE_METATYPE(GroupData) #endif diff --git a/src/server/MessageHandler.cpp b/src/server/MessageHandler.cpp index 92896cdb5..f15e78adb 100644 --- a/src/server/MessageHandler.cpp +++ b/src/server/MessageHandler.cpp @@ -1,249 +1,257 @@ /* GComprisServer - MessageHandler.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 #include "Server.h" #include "Database.h" #include "MessageHandler.h" MessageHandler* MessageHandler::_instance = 0; MessageHandler::MessageHandler() { Server &server = *Server::getInstance(); connect(&server, &Server::loginReceived, this, &MessageHandler::onLoginReceived); connect(&server, &Server::activityDataReceived, this, &MessageHandler::onActivityDataReceived); connect(&server, &Server::newClientReceived, this, &MessageHandler::onNewClientReceived); connect(&server, &Server::clientDisconnected, this, &MessageHandler::onClientDisconnected); - // todo synchronize with database - // Synchronize user list - QList allUsers; - Database::getInstance()->retrieveAllExistingUsers(allUsers); - for(UserData *u: allUsers) { - m_users.push_back((QObject*)u); + // retrieve all the existing users and groups + QList groups; + Database::getInstance()->retrieveAllExistingGroups(groups); + for(auto it=groups.begin(); it!= groups.end(); it++){ + m_groups.push_back((QObject*)(*it)); } emit newUsers(); - // For test purposes, to be removed later - GroupData *group = createGroup("default"); - UserData *user1 = createUser("a", "", { "default" }); - UserData *user2 = createUser("b", ""); - group->addUser(user2); // same as putting directly the group } MessageHandler* MessageHandler::getInstance() { if (!_instance) _instance = new MessageHandler; return _instance; } QObject *MessageHandler::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void MessageHandler::init() { + getInstance(); qmlRegisterType(); qmlRegisterType(); qmlRegisterSingletonType("GCompris", 1, 0, "MessageHandler", systeminfoProvider); } -GroupData *MessageHandler::createGroup(const QString &newGroup, const QStringList &users) +GroupData *MessageHandler::createGroup(const QString &newGroup,const QString &description, + const QStringList& users) { - qDebug() << "createGroup '" << newGroup << "'"; - GroupData *c = new GroupData(); - c->setName(newGroup); - for(const QString &username: users) - c->addUser(getUser(username)); - m_groups.push_back((QObject*)c); - emit newGroups(); - return c; -} + //1. add group to database + //2. make a new a group and add it to m_groups; + if(Database::getInstance()->addGroup(newGroup, description,users)){ + GroupData *c = new GroupData(); + c->setName(newGroup); + c->setDescription(description); + m_groups.push_back((QObject*)c); + qDebug() << "size of the list after adding the new group is " << m_groups.length(); + emit newGroups(); + return c; + } -GroupData *MessageHandler::updateGroup(const QString &oldGroupName, const QString &newGroupName, const QStringList &users) -{ - qDebug() << "updateGroup '" << newGroupName << "'"; - GroupData *c = getGroup(oldGroupName); - c->setName(newGroupName); + return nullptr; - c->removeAllUsers(); - for(const QString &aUser: users) { - UserData *user = getUser(aUser); - c->addUser(user); - } - emit newGroups(); - return c; } void MessageHandler::deleteGroup(const QString &groupName) { - qDebug() << "deleteGroup '" << groupName << "'"; - GroupData *c = getGroup(groupName); - qDebug() << c; - m_groups.removeAll(c); - delete c; - emit newGroups(); + //delete from database + if(Database::getInstance()->deleteGroup(groupName)){ + GroupData *c = getGroup(groupName); + qDebug() << c; + m_groups.removeAll(c); + delete c; + emit newGroups(); + + } + else{ + qDebug() << "could not delete the group from database"; + } + + } UserData *MessageHandler::createUser(const QString &newUser, const QString &avatar, const QStringList &groups) { - if(Database::getInstance()->addUser(newUser, avatar)) { + +// Add the user in the database +/* if(Database::getInstance()->addUser(newUser, avatar, groups)){ qDebug() << "createUser '" << newUser << "' in groups " << groups; UserData *u = new UserData(); u->setName(newUser); u->setAvatar(avatar); for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); - group->addUser(u); + if(group){ + group->addUser(u); + u->addGroup(group); + } } m_users.push_back((QObject*)u); emit newUsers(); return u; } else { qDebug() << "Error while creating user " << newUser; } - return nullptr; + return nullptr;*/ } UserData *MessageHandler::updateUser(const QString &oldUser, const QString &newUser, const QString &avatar, const QStringList &groups) { UserData *user = getUser(oldUser); if (user) { user->setName(newUser); // for each group, remove the user if not in the new groups and add it in the new ones removeUserFromAllGroups(user); for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); group->addUser(user); } emit newUsers(); } return user; } UserData *MessageHandler::getUser(const QString &userName) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { return user; } } return nullptr; } GroupData *MessageHandler::getGroup(const QString &groupName) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; if (group->getName() == groupName) { return group; } } return nullptr; } void MessageHandler::removeUserFromAllGroups(UserData *user) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; group->removeUser(user); } } void MessageHandler::deleteUser(const QString &userName) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { m_users.removeAll(user); removeUserFromAllGroups(user); delete user; emit newUsers(); } } } -void MessageHandler::onLoginReceived(const ClientData &who, const Login &data) +void MessageHandler::onLoginReceived(QTcpSocket *socket, const Login &data) { qDebug() << "Login received '" << data._name << "'"; - ClientData *c = getClientData(who); + ClientData *c = getClientData(socket); + + for(QObject *oClient: m_clients ) { + ClientData *c = (ClientData*)oClient; + if(c->getUserData() && c->getUserData()->getName() == data._name) { + // found a client with the same user name.(i.e someone chose the wrong login) + qDebug() << "a client with the same user name already exists"; + return; + //todo: + // return an error message to client and inform that you have chosen the wrong login + } + } for(QObject *oUser: m_users) { UserData *user = (UserData*)oUser; - // todo Check that we don't find a client with the same login! else it will probably means a child didn't choose the good login - - qDebug() << user->getName() << data._name; + qDebug() << "recieved login " << data._name << " " << c->getSocket(); if(user->getName() == data._name) { c->setUser(user); return; } } // Should not happen when login will be done properly... todo display an error message qDebug() << "Error: login " << data._name << " received, but no user found"; } void MessageHandler::onActivityDataReceived(const ClientData &who, const ActivityRawData &act) { qDebug() << "Activity: " << act.activityName << ", date: " << act.date << ", data:" << act.data << ", user: " << act.username; UserData *u = getUser(act.username); u->addData(act); } void MessageHandler::onNewClientReceived(const ClientData &client) { qDebug() << "New client"; ClientData *c = new ClientData(client); m_clients.push_back((QObject*)c); emit newClients(); } -void MessageHandler::onClientDisconnected(const ClientData &client) +void MessageHandler::onClientDisconnected(QTcpSocket* socket) { qDebug() << "client disconnected"; - ClientData *c = getClientData(client); + ClientData *c = getClientData(socket); c->setUser(nullptr); m_clients.removeAll(c); delete c; emit newClients(); } -ClientData *MessageHandler::getClientData(const ClientData &cd) +ClientData *MessageHandler::getClientData(QTcpSocket* socket) { - const QTcpSocket *socket = cd.getSocket(); for (QObject *oc: m_clients) { ClientData *c = (ClientData *) oc; if (c->getSocket() == socket) { return c; } } } diff --git a/src/server/MessageHandler.h b/src/server/MessageHandler.h index 28d02d87f..3b4b57bf1 100644 --- a/src/server/MessageHandler.h +++ b/src/server/MessageHandler.h @@ -1,98 +1,116 @@ /* GComprisServer - MessageHandler.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 MESSAGEHANDLER_H #define MESSAGEHANDLER_H #include "Messages.h" #include "ClientData.h" #include "GroupData.h" #include "UserData.h" #include #include /** * @class MessageHandler * @short Handles all the messages received by the socket * - * This class contains all the users/groups/clients and handles all the - * messages received by the server. + * JOB: + * -Handle messages received by the socket + * -Create,delete, Update information about: + * --users + * --groups + * -Linking Users,Clients and groups with each other + * * * @sa UserData * @sa GroupData * @sa ClientData */ class MessageHandler: public QObject { Q_OBJECT Q_PROPERTY(QList clients MEMBER m_clients NOTIFY newClients) Q_PROPERTY(QList groups MEMBER m_groups NOTIFY newGroups) Q_PROPERTY(QList users MEMBER m_users NOTIFY newUsers) private: MessageHandler(); // prohibit external creation, we are a singleton! static MessageHandler* _instance; // singleton instance public: /** * Registers MessageHandler singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static MessageHandler* getInstance(); - Q_INVOKABLE GroupData *createGroup(const QString &groupName, const QStringList &users = QStringList()); - Q_INVOKABLE GroupData *updateGroup(const QString &oldGroupName, const QString &newGroupName, const QStringList &users = QStringList()); + Q_INVOKABLE GroupData *createGroup(const QString &groupName,const QString &description=QString(), + const QStringList& users=QStringList()); Q_INVOKABLE void deleteGroup(const QString &groupName); Q_INVOKABLE UserData *createUser(const QString &userName, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE UserData *updateUser(const QString &oldUser, const QString &newUser, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE void deleteUser(const QString &userName); UserData *getUser(const QString &userName); GroupData *getGroup(const QString &groupName); + + + Q_INVOKABLE QList returnGroupUsers(const QString& group){ + + GroupData* g = getGroup(group); + if(g){ + return g->getUsers(); + } + } + + + + public slots: - void onLoginReceived(const ClientData &who, const Login &data); void onActivityDataReceived(const ClientData &who, const ActivityRawData &act); + void onLoginReceived(QTcpSocket* socket, const Login &data); void onNewClientReceived(const ClientData &client); - void onClientDisconnected(const ClientData &client); + void onClientDisconnected(QTcpSocket* socket); signals: void newClients(); void newGroups(); void newUsers(); private: - ClientData *getClientData(const ClientData &cd); + ClientData *getClientData(QTcpSocket* socket); void removeUserFromAllGroups(UserData *user); // ClientData* QList m_clients; // GroupData* QList m_groups; // UserData* QList m_users; }; #endif diff --git a/src/server/Server.cpp b/src/server/Server.cpp index 749a760ee..8f96fdea4 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -1,274 +1,216 @@ /* GCompris - Server.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 #include "Messages.h" #include "DataStreamConverter.h" #include "ClientData.h" #include "GroupData.h" #include "Server.h" +#include "MessageHandler.h" #include Server* Server::_instance = 0; Server::Server(QObject *parent) : QObject(parent) , tcpServer(Q_NULLPTR) , networkSession(0) { udpSocket = new QUdpSocket(this); messageNo = 1; + tcpServer = new QTcpServer(this); + connect(tcpServer, &QTcpServer::newConnection, this, &Server::newTcpConnection); - QNetworkConfigurationManager manager; - if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired) { - // Get saved network configuration - QSettings settings(QSettings::UserScope, QLatin1String("QtProject")); - settings.beginGroup(QLatin1String("QtNetwork")); - const QString id = settings.value(QLatin1String("DefaultNetworkConfiguration")).toString(); - settings.endGroup(); - - // If the saved network configuration is not currently discovered use the system default - QNetworkConfiguration config = manager.configurationFromIdentifier(id); - if ((config.state() & QNetworkConfiguration::Discovered) != - QNetworkConfiguration::Discovered) { - config = manager.defaultConfiguration(); - } - - networkSession = new QNetworkSession(config, this); - connect(networkSession, &QNetworkSession::opened, this, &Server::sessionOpened); - - networkSession->open(); - } - else { - sessionOpened(); + if (!tcpServer->listen(QHostAddress::Any, 5678)) { + qDebug() << tr("Unable to start the server: %1.") + .arg(tcpServer->errorString()); } } Server* Server::getInstance() { if (!_instance) _instance = new Server; return _instance; } QObject *Server::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void Server::init() { qmlRegisterSingletonType("GCompris", 1, 0, "Server", systeminfoProvider); } -void Server::sessionOpened() -{ - // Save the used configuration - if (networkSession) { - QNetworkConfiguration config = networkSession->configuration(); - QString id; - if (config.type() == QNetworkConfiguration::UserChoice) - id = networkSession->sessionProperty(QLatin1String("UserChoiceConfiguration")).toString(); - else - id = config.identifier(); - - QSettings settings(QSettings::UserScope, QLatin1String("QtProject")); - settings.beginGroup(QLatin1String("QtNetwork")); - settings.setValue(QLatin1String("DefaultNetworkConfiguration"), id); - settings.endGroup(); - } - - tcpServer = new QTcpServer(this); - connect(tcpServer, &QTcpServer::newConnection, this, &Server::newTcpConnection); - - if (!tcpServer->listen(QHostAddress::Any, 5678)) { - qDebug() << tr("Unable to start the server: %1.") - .arg(tcpServer->errorString()); - return; - } - QString ipAddress; - QList ipAddressesList = QNetworkInterface::allAddresses(); - // use the first non-localhost IPv4 address - for (int i = 0; i < ipAddressesList.size(); ++i) { - if (ipAddressesList.at(i) != QHostAddress::LocalHost && - ipAddressesList.at(i).toIPv4Address()) { - ipAddress = ipAddressesList.at(i).toString(); - break; - } - } - // if we did not find one, use IPv4 localhost - if (ipAddress.isEmpty()) - ipAddress = QHostAddress(QHostAddress::LocalHost).toString(); -} - void Server::newTcpConnection() { QTcpSocket *clientConnection = tcpServer->nextPendingConnection(); qDebug() << clientConnection; connect(clientConnection, &QAbstractSocket::disconnected, this, &Server::disconnected); connect(clientConnection, &QAbstractSocket::readyRead, this, &Server::slotReadyRead); list.push_back(clientConnection); qDebug() << clientConnection->peerAddress().toString(); ClientData newClient; newClient.setSocket(clientConnection); qDebug("New tcp connection"); emit newClientReceived(newClient); } void Server::broadcastDatagram() { qDebug()<< QHostInfo::localHostName(); QByteArray datagram; // todo use a real message structure in Messages.h QDataStream out(&datagram, QIODevice::WriteOnly); out << MessageIdentifier::REQUEST_CONTROL << QHostInfo::localHostName(); qint64 data = udpSocket->writeDatagram(datagram.data(),datagram.size(),QHostAddress::Broadcast, 5678); qDebug()<< " size of data :" << data; } void Server::slotReadyRead() { QTcpSocket* clientConnection = qobject_cast(sender()); QByteArray data = clientConnection->readAll(); QDataStream in(&data, QIODevice::ReadOnly); in.setVersion(QDataStream::Qt_4_0); ClientData client; client.setSocket(clientConnection); while(!in.atEnd()) { Identifier messageId; in >> messageId; switch(messageId._id) { case MessageIdentifier::LOGIN: { Login log; in >> log; - emit loginReceived(client, log); + emit loginReceived(clientConnection, log); break; } case MessageIdentifier::ACTIVITY_DATA: { ActivityRawData act; in >> act; emit activityDataReceived(client, act); break; } default: qDebug() << messageId._id << " received but not handled"; } } } void Server::disconnected() { QTcpSocket* clientConnection = qobject_cast(sender()); if(!clientConnection) return; qDebug() << "Removing " << clientConnection; list.removeAll(clientConnection); - ClientData newClient; - newClient.setSocket(clientConnection); - emit clientDisconnected(newClient); + emit clientDisconnected(clientConnection); clientConnection->deleteLater(); } void Server::sendActivities() { DisplayedActivities activities; activities.activitiesToDisplay << "geography/Geography.qml" << "erase/Erase.qml" << "reversecount/Reversecount.qml"; QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out << DISPLAYED_ACTIVITIES << activities; for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } } void Server::sendLoginList(QObject *g) { GroupData *group = (GroupData *) g; // Get all the clients from MessageHandler // For each client, if it does not have a name yet, send the message QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); AvailableLogins act; for(const QObject *oC: group->getUsers()) { act._logins << ((const UserData*)oC)->getName(); } // todo remove already used logins out << LOGINS_LIST << act; for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } //qDebug() << "Sending " << block << " to " << client->getSocket(); //((QTcpSocket*)client->getSocket())->write(block); } void Server::sendConfiguration(/*QObject *c*//*, const ConfigurationData &config*/) { //ClientData *client = (ClientData*)c; QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); ActivityConfiguration act; act.activityName = "reversecount/Reversecount.qml"; act.data["dataset"] = "[{\"maxNumber\": 1, \"minNumber\": 1, \"numberOfFish\": 1}," " {\"maxNumber\": 2, \"minNumber\": 2, \"numberOfFish\": 4}]"; out << ACTIVITY_CONFIGURATION << act; for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } //qDebug() << "Sending " << block << " to " << client->getSocket(); //((QTcpSocket*)client->getSocket())->write(block); } diff --git a/src/server/Server.h b/src/server/Server.h index 98bff6cb3..e95711c78 100644 --- a/src/server/Server.h +++ b/src/server/Server.h @@ -1,97 +1,96 @@ /* GCompris - Server.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 SERVER_H #define SERVER_H class QTcpServer; class QTcpSocket; class QNetworkSession; class QUdpSocket; struct Login; struct ActivityRawData; struct GroupData; struct ClientData; #include #include /** * @class Server * @short Receive and send messages to the gcompris client instances * * Contains the tcp socket that sends and receives the different messages to * the clients. * Sends the following messages: * * LoginList: list of all the logins the client can choose * * DisplayedActivities: activities to display on the target client * * ActivityConfiguration: for one activity, send a specific configuration (dataset) * * Receives the following ones: * * Login: allows to identify a client with a name * * ActivityData: contains the data for one result activity send by a client * * @sa MessageHandler */ class Server : public QObject { Q_OBJECT private: explicit Server(QObject *parent = Q_NULLPTR); static Server* _instance; // singleton instance public: /** * Registers Server singleton in the QML engine. */ /// @cond INTERNAL_DOCS static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static Server* getInstance(); Q_INVOKABLE void broadcastDatagram(); Q_INVOKABLE void sendConfiguration(); Q_INVOKABLE void sendActivities(); Q_INVOKABLE void sendLoginList(QObject *g); private slots: - void sessionOpened(); void newTcpConnection(); void slotReadyRead(); void disconnected(); private: QTcpServer *tcpServer; QNetworkSession *networkSession; QList list; QUdpSocket *udpSocket; int messageNo; signals: void newClientReceived(const ClientData &client); - void clientDisconnected(const ClientData &client); - void loginReceived(const ClientData &who, const Login &log); void activityDataReceived(const ClientData &who, const ActivityRawData &data); + void clientDisconnected(QTcpSocket* socket); + void loginReceived(QTcpSocket* socket, const Login &log); }; #endif diff --git a/src/server/views/Groups.qml b/src/server/views/Groups.qml index 5d2152fe1..3387bf8d1 100644 --- a/src/server/views/Groups.qml +++ b/src/server/views/Groups.qml @@ -1,231 +1,507 @@ /* GCompris - Groups.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 3 of the License, or * (at your option) any later version. * * 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 . */ import QtQuick 2.1 import GCompris 1.0 import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 import "../../core" ActivityBase { id: activity activityInfo: QtObject { property bool demo: false } pageComponent: Item { + id: mainItem anchors.fill: parent + property string groupName: groupText.text + property var users: [] + property string groupDescription: descriptionText.text + property string mode:"" GridView { id: clients width: activity.width height: activity.height cellWidth: 210 cellHeight: cellWidth model: MessageHandler.groups + property string currentGroup: "" highlight: Rectangle { color: "lightsteelblue"; radius: 5 } delegate: Rectangle { id: itemDelegate width: 200 height: 200 color: "red" property string name: modelData.name GCText { text: modelData.name } MouseArea { id: mouse anchors.fill: parent - onClicked: { clients.currentIndex = index ; print(modelData.name) } // todo what do we do? display list of action? (update user list, send configuration?) + onClicked: { clients.currentIndex = index; + clients.currentGroup = name + print(modelData.name) + groupConfig.visible = true + configView.visible = true + cancelButton.visible = true + } } } } - Grid { rows: 1 anchors.bottom: bar.top Button { id: createGroupButton text: qsTr("Create a Group") style: GCButtonStyle {} onClicked: { - createGroupName.mode = "create"; - createGroupName.visible = true; - createGroupName.defaultText = ""; - createGroupName.start(); + createGroupItem.visible = true; } } - Button { - id: updateGroupButton - text: qsTr("Update a Group") - style: GCButtonStyle {} - onClicked: { - if(clients.currentItem) { - createGroupName.mode = "update"; - createGroupName.visible = true; - createGroupName.defaultText = clients.currentItem.name; - createGroupName.start(); + } + Item{ + id: groupConfig + visible: false + width: parent.width/2.5 + height: parent.height/1.75 + anchors.centerIn: parent + Rectangle{ + id: baseRect + anchors.fill: parent + color: "grey" + opacity: 0.6 + } + + + ListView{ + id: configView + anchors.fill: parent + visible: true + property var list : [ + { + "shortName": "addUsers", + "text": qsTr("add new users to %1").arg(clients.currentGroup) + }, + { + "shortName": "showUsers", + "text": qsTr("show users belonging to %1").arg(clients.currentGroup) + }, + { + "shortName": "description", + "text": qsTr("update description about the group") + }, + { + "shortName" : "deleteUsers", + "text" : qsTr("delete users from the group %1").arg(clients.currentGroup) + }, + { + "shortName" : "deleteGroup", + "text": qsTr("delete this group ") + }, + { + "shortName" : "changeName", + "text" : qsTr("change name of this group ") + } + + ] + model:list + spacing: 3 + delegate: Button{ + id: actions + Text { + text: modelData.text + anchors.centerIn: parent + color: "black" + font.pixelSize: 22 + } + width: parent.width + height: 70 + style: GCButtonStyle{} + // anchors.horizontalCenter: parent.horizontalCenter + MouseArea{ + anchors.fill: parent + onClicked: { + cancelButton.visible = false + configView.visible = false + if(modelData.shortName === "addUsers" ) + { + usersView.visible = true + usersView.model = MessageHandler.users + addNewUsersButton.visible = true + goBackButton.visible = true + + } + else if(modelData.shortName === "showUsers"){ + usersView.model = MessageHandler.returnGroupUsers(clients.currentGroup) + addNewUsersButton.visible = false + goBackButton.visible = true + usersView.visible = true + + } + else if(modelData.shortName === "deleteGroup"){ + groupConfig.visible = false + confirmationBox.visible = true + mainItem.mode = "deleteGroup" + + } + + } + } + + } - enabled: clients.currentItem && clients.currentIndex != -1 } - Button { - id: deleteGroupButton - text: qsTr("Delete selected group") - style: GCButtonStyle {} - onClicked: { - if(clients.currentItem) { - MessageHandler.deleteGroup(clients.currentItem.name); + + ListView{ + id: usersView + anchors.fill: parent + visible: false + model: undefined + spacing: 50 + delegate: checkBoxDelegate + + } + Button{ + id: addNewUsersButton + width:parent.width/2 + visible: false + height: parent.height/6 + style: GCButtonStyle{} + anchors.bottom: parent.bottom + text: qsTr("Add") + MouseArea{ + anchors.fill: parent + onClicked: { + mainItem.mode = "addUsers"; + groupConfig.visible = false + addNewUsersButton.visible = false + goBackButton.visible = false + usersView.visible = false + confirmationBox.visible = true + + + } } - enabled: clients.currentItem && clients.currentIndex != -1 } - - Button { - id: sendConfiguration - text: qsTr("Send user list") - style: GCButtonStyle {} - onClicked: { - print("select config and send config to: " + clients.currentItem.name); - Server.sendLoginList(MessageHandler.groups[clients.currentIndex]); + Button{ + id: goBackButton + width:addNewUsersButton.visible ? parent.width/2: parent.width + visible: false + height: parent.height/6 + style: GCButtonStyle{} + anchors.bottom: parent.bottom + anchors.right: parent.right +// anchors.left: addNewUsersButton.visible? addNewUsersButton.right : undefined + text: qsTr("Back") + MouseArea{ + anchors.fill: parent + onClicked:{ + addNewUsersButton.visible = false + goBackButton.visible = false + usersView.visible = false + configView.visible = true + cancelButton.visible = true + } } - enabled: clients.currentIndex != -1 } + + + + } - GCInputDialog { - id: createGroupName + GCButtonCancel{ + id: cancelButton visible: false - active: visible - anchors.fill: parent - z: 100 - property string mode: "create" - message: mode == "create" ? qsTr("Name of the new group") : qsTr("Update group %1").arg(clients.currentItem.name) - onClose: createGroupName.visible = false; - button1Text: qsTr("OK") - button2Text: qsTr("Cancel") - onButton1Hit: { - if(MessageHandler.users.length !== 0) { - chooseLogin.visible = true; - chooseLogin.groupname = createGroupName.inputtedText - chooseLogin.start(); - } - else { - // no users, create the group directly - if(mode == "create") { - MessageHandler.createGroup(createGroupName.inputtedText) - } - else { - MessageHandler.updateGroup(clients.currentItem.name, createGroupName.inputtedText) + anchors.right: undefined + anchors.top: undefined + anchors.bottom: groupConfig.top + anchors.left: groupConfig.right + anchors.margins: 0 + onClose:{ + cancelButton.visible = false + groupConfig.visible = false + } + } + + Item{ + id: createGroupItem + visible: false + width:parent.width/1.75 + height: parent.height/1.5 + anchors.centerIn: parent + Rectangle{ + id: createGroupRect + anchors.fill: parent + color: "grey" + opacity: 0.6 + Button{ + id: groupName + anchors.left: parent.left + height: parent.height/8 + width: parent.width/4 + + text: qsTr("Group Name") + + style: GCButtonStyle{} + } + TextField{ + id: groupText + anchors.left: groupName.right + anchors.leftMargin: 30 + anchors.right: parent.right + height: groupName.height + text: "" + placeholderText: qsTr("Enter the group Name") + } + + Button{ + id: description + anchors.left: parent.left + anchors.top: groupName.bottom + anchors.topMargin: 50 + height: parent.height/8 + // anchors.verticalCenter: parent.verticalCenter + width: parent.width/4 + text: qsTr("Description") + style: GCButtonStyle{} + } + TextField{ + id: descriptionText + anchors.left: description.right + anchors.leftMargin: 30 + anchors.top: groupText.bottom + anchors.topMargin: 50 + anchors.right: parent.right + height: description.height + text: "" + placeholderText: qsTr("Add description about the group") + } + Button{ + id: addUsers + height: parent.height/8 + anchors.left: parent.left + anchors.topMargin: 50 + + anchors.top: description.bottom + width: parent.width/4 + text: qsTr("Add Users") + style: GCButtonStyle{} + } + + ListView{ + id: chooseUsers + anchors.left: addUsers.right + anchors.right: parent.right + anchors.top: descriptionText.bottom + anchors.topMargin: 50 + anchors.leftMargin: 30 + height: parent.height/4 + model: MessageHandler.users + spacing: 50 + Component{ + id: checkBoxDelegate + GCDialogCheckBox{ + id: checkbox + width: parent.width + text:modelData.name + onCheckedChanged:{ + if(checkbox.checked){ + console.log("checked ",modelData.name) + mainItem.users.push(modelData.name) + } + + else{ + mainItem.users.pop(modelData.name) + console.log("unchecked ", modelData.name) + } + } + + style: CheckBoxStyle { + spacing: 10 + + indicator: Image { + sourceSize.height: 50 + property string suffix: control.enabled ? ".svg" : "_disabled.svg" + source: + control.checked ? "qrc:/gcompris/src/core/resource/apply" + suffix : + "qrc:/gcompris/src/core/resource/cancel" + suffix + } + label: GCText { + fontSize: mediumSize + text: control.text + wrapMode: Text.WordWrap + width: parent.parent.width - 50 * ApplicationInfo.ratio - 10 * 2 + } + + + } + } } + + delegate: checkBoxDelegate + } } - focus: true - onStart: { - inputItem.text = defaultText; - inputItem.forceActiveFocus(); + + + Button{ + id: createButton + anchors.bottom: parent.bottom + height: parent.height/6 + width: parent.width/2 + style: GCButtonStyle{} + text: qsTr("Create") + MouseArea{ + anchors.fill: parent + onClicked:{ + //show the confirmation box + mainItem.mode = "create" + createGroupItem.visible = false + confirmationBox.visible = true + } + } } - onStop: activity.forceActiveFocus() - - /** - * type:string - * inputted default text in the TextInput. - */ - property string defaultText - - /** - * type:string - * inputted text in the TextInput. - */ - property string inputtedText: inputItem ? inputItem.text : "" - - content: TextInput { - id: inputItem - height: 60 * ApplicationInfo.ratio - horizontalAlignment: TextInput.AlignHCenter - verticalAlignment: TextInput.AlignVCenter - text: createGroupName.defaultText - font.pointSize: 14 - font.weight: Font.DemiBold + Button{ + id: cancel + anchors.bottom: parent.bottom + height: parent.height/6 + anchors.left: createButton.right + width: parent.width/2 + style: GCButtonStyle{} + text: qsTr("Cancel") + MouseArea{ + anchors.fill: parent + onClicked:{ + createGroupItem.visible = false + + } + } + } + + + } - GCInputDialog { - id: chooseLogin + Rectangle{ + id: confirmationBox + width: parent.width/2 + height: parent.height/4.5 + anchors.centerIn: parent + color: "grey" + opacity: 0.6 visible: false - active: visible - anchors.fill: parent - - message: qsTr("Add users to group") - onClose: chooseLogin.visible = false; + GCText{ + text: { + if (mainItem.mode === "create") + return "Are you sure you want to create this group?" + else if (mainItem.mode === "addUsers") + return "Are you sure you want to add these users to this group?" + else if(mainItem.mode === "deleteGroup") + return "Are you sure you want to delete the group " + clients.currentGroup + else + return "" + } - property string groupname + wrapMode: Text.Wrap + font.pixelSize: 25 + anchors.centerIn: parent - button1Text: qsTr("OK") - onButton1Hit: { - createGroupName.mode == "create" ? - MessageHandler.createGroup(groupname, selectedUsers) : - MessageHandler.updateGroup(clients.currentItem.name, groupname, selectedUsers) - chooseLogin.selectedUsers = []; } - focus: true - - property string chosenLogin - property var model: MessageHandler.users - - property var selectedUsers: [] - content: ListView { - id: view - width: chooseLogin.width - height: 100 * ApplicationInfo.ratio - contentHeight: 60 * ApplicationInfo.ratio * model.count - interactive: true - clip: true - model: chooseLogin.model - delegate: GCDialogCheckBox { - id: userBox - text: modelData.name - // if you create a user, it's not in any group - // (need to handle case of existing name) - checked: createGroupName.mode == "create" ? false : - clients.model[clients.currentIndex].hasUser(modelData.name) - onCheckedChanged: { - if(checked) { - chooseLogin.selectedUsers.push(modelData.name) + Button{ + id: yes + anchors.bottom: parent.bottom + height: parent.height/6 + width: parent.width/2 + style: GCButtonStyle{} + text: qsTr("YES") + MouseArea{ + anchors.fill: parent + onClicked: { + + if (mainItem.mode === "create") + { + console.log(mainItem.groupName," ",mainItem.groupDescription, " ", + mainItem.users) + MessageHandler.createGroup(mainItem.groupName, mainItem.groupDescription, + mainItem.users) + } + if (mainItem.mode === "addUsers") + { + console.log("add new users to group ",clients.currentGroup, mainItem.users) + MessageHandler.addUserToGroup(clients.currentGroup,mainItem.users) + } - else { - chooseLogin.selectedUsers.splice(chooseLogin.selectedUsers.indexOf(modelData.name), 1) + if(mainItem.mode === "deleteGroup"){ + console.log("deleting group ",clients.currentGroup) + MessageHandler.deleteGroup(clients.currentGroup) } + + groupText.text = "" + descriptionText.text = "" + mainItem.users = [] + confirmationBox.visible = false + + } + } + } + Button{ + id: no + anchors.bottom: parent.bottom + height: parent.height/6 + anchors.left: yes.right + width: parent.width/2 + style: GCButtonStyle{} + text: qsTr("NO") + MouseArea{ + anchors.fill: parent + onClicked:{ + // set all the values to "" + groupText.text = "" + descriptionText.text = "" + mainItem.users = [] + confirmationBox.visible = false } } } } + Bar { id: bar content: BarEnumContent { value: home } onHomeClicked: activity.home() } } } +