From 431c1dcded16649c10331b9dc4e57f20067cea0b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 1 May 2018 13:27:52 +0200 Subject: Add 'add user', 'join channel'. Improve scrolling. Added locks --- depends/odhtdb | 2 +- include/Channel.hpp | 9 +- include/MessageBoard.hpp | 1 + src/Channel.cpp | 67 ++++++++++++- src/MessageBoard.cpp | 34 ++++++- src/main.cpp | 251 ++++++++++++++++++++++++++++++++++++++--------- 6 files changed, 303 insertions(+), 61 deletions(-) diff --git a/depends/odhtdb b/depends/odhtdb index 670e3ee..8da5ba7 160000 --- a/depends/odhtdb +++ b/depends/odhtdb @@ -1 +1 @@ -Subproject commit 670e3eed2703dcee1dee0508e45d8454cae78544 +Subproject commit 8da5ba7f057978b224868028679cbda9e11089ab diff --git a/include/Channel.hpp b/include/Channel.hpp index 5650eb1..be6e7c7 100644 --- a/include/Channel.hpp +++ b/include/Channel.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace odhtdb { @@ -20,18 +21,24 @@ namespace dchat public: Channel(const std::string &name, const odhtdb::DatabaseNode &databaseNodeInfo = odhtdb::DatabaseNode(), User *localUser = nullptr, odhtdb::Database *database = nullptr); virtual ~Channel(); + Channel(const Channel& other) = delete; + Channel& operator = (const Channel &other) = delete; User* getLocalUser(); + SystemUser* getSystemUser(); MessageBoard& getMessageBoard(); const std::string& getName() const; const std::vector getUsers() const; User* getUserByPublicKey(const odhtdb::Signature::PublicKey &publicKey); + std::shared_ptr getId(); // If timestamp is 0, then timestamp is not used void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds = 0); void addMessage(const std::string &msg); - void addUser(User *user); + void addUserLocally(User *user); + bool addUser(const odhtdb::Signature::PublicKey &userId, const std::string &groupId); + void replaceLocalUser(User *newLocalUser); void processEvent(const sf::Event &event); void draw(sf::RenderWindow &window, Cache &cache); diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp index 739f161..e84396d 100644 --- a/include/MessageBoard.hpp +++ b/include/MessageBoard.hpp @@ -36,5 +36,6 @@ namespace dchat double scrollSpeed; sf::Clock frameTimer; double totalHeight; + bool scrollToBottom; }; } diff --git a/src/Channel.cpp b/src/Channel.cpp index 748be49..b922833 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -16,7 +16,7 @@ namespace dchat messageBoard(sf::Vector2u(1.0f, 1.0f)), localUser(_localUser ? _localUser : new OfflineUser("You")) { - addUser(localUser); + addUserLocally(localUser); { Message *message = new Message(&systemUser, u8"hello, worldåäö1![emoji](https://discordemoji.com/assets/emoji/playtime.png)"); messageBoard.addMessage(message); @@ -72,6 +72,11 @@ namespace dchat return localUser; } + SystemUser* Channel::getSystemUser() + { + return &systemUser; + } + MessageBoard& Channel::getMessageBoard() { return messageBoard; @@ -95,6 +100,11 @@ namespace dchat return nullptr; } + std::shared_ptr Channel::getId() + { + return databaseNodeInfo.getRequestHash(); + } + void Channel::addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds) { assert(owner); @@ -106,16 +116,16 @@ namespace dchat if(database && localUser->type == User::Type::ONLINE) { addLocalMessage(msg, localUser, database->getSyncedTimestampUtc().seconds); - auto onlineUser = static_cast(localUser); - assert(onlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL); - database->addData(databaseNodeInfo, static_cast(onlineUser->databaseUser), odhtdb::DataView((void*)msg.data(), msg.size())); + auto localOnlineUser = static_cast(localUser); + assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL); + database->addData(databaseNodeInfo, static_cast(localOnlineUser->databaseUser), odhtdb::DataView((void*)msg.data(), msg.size())); database->commit(); } else addLocalMessage(msg, localUser, 0); } - void Channel::addUser(User *user) + void Channel::addUserLocally(User *user) { users.push_back(user); if(user->type == User::Type::ONLINE) @@ -125,6 +135,53 @@ namespace dchat } } + bool Channel::addUser(const odhtdb::Signature::PublicKey &userId, const string &groupId) + { + assert(database); + if(!database || localUser->type != User::Type::ONLINE) + return false; + + if(groupId.size() != odhtdb::GROUP_ID_LENGTH) + { + fprintf(stderr, "Group id is wrong size. Expected to be %u bytes, was %u byte(s)\n", odhtdb::GROUP_ID_LENGTH, groupId.size()); + return false; + } + + auto localOnlineUser = static_cast(localUser); + assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL); + + uint8_t groupIdRaw[odhtdb::GROUP_ID_LENGTH]; + memcpy(groupIdRaw, groupId.data(), groupId.size()); + auto groupToAddUserTo = database->getStorage().getGroupById(*databaseNodeInfo.getRequestHash(), groupIdRaw); + if(!groupToAddUserTo) + { + fprintf(stderr, "Group with id %s does not exist in channel %s\n", groupId.c_str(), databaseNodeInfo.getRequestHash()->toString().c_str()); + return false; + } + database->addUser(databaseNodeInfo, static_cast(localOnlineUser->databaseUser), "noname", userId, groupToAddUserTo); + + auto addedUser = database->getStorage().getUserByPublicKey(*databaseNodeInfo.getRequestHash(), userId); + assert(addedUser); + addUserLocally(new OnlineUser(addedUser)); + return true; + } + + void Channel::replaceLocalUser(User *newLocalUser) + { + for(vector::iterator it = users.begin(); it != users.end(); ++it) + { + if(*it == localUser) + { + users.erase(it); + delete localUser; + break; + } + } + + localUser = newLocalUser; + users.push_back(newLocalUser); + } + void Channel::processEvent(const sf::Event &event) { chatbar.processEvent(event, this); diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index bcdf5c7..031bbdf 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -61,7 +61,8 @@ namespace dchat leftMouseButtonPressed(false), scroll(0.0), scrollSpeed(0.0), - totalHeight(0.0) + totalHeight(0.0), + scrollToBottom(false) { updateStaticContentTexture(size); } @@ -85,6 +86,7 @@ namespace dchat { messages.push_back(message); dirty = true; + scrollToBottom = true; } void MessageBoard::processEvent(const sf::Event &event) @@ -118,7 +120,7 @@ namespace dchat } else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) { - scrollSpeed += (event.mouseWheelScroll.delta * 30.0); + scrollSpeed += (event.mouseWheelScroll.delta * 5.0); } if(selectingText && !leftMouseButtonPressed) @@ -186,9 +188,10 @@ namespace dchat { time_t time = (time_t)message->timestampSeconds; struct tm *localTimePtr = localtime(&time); - char *timeStr = asctime(localTimePtr); + char date[30]; + strftime(date, sizeof(date), "%Y-%m-%d at %T", localTimePtr); - sf::Text timestamp(timeStr, *timestampFont, timestampTextCharacterSize); + sf::Text timestamp(date, *timestampFont, timestampTextCharacterSize); timestamp.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 30)); timestamp.setPosition(sf::Vector2f(floor(position.x + PADDING_SIDE + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f))); window.draw(timestamp); @@ -216,7 +219,19 @@ namespace dchat } scroll += scrollSpeed; - scrollSpeed /= (deltaTimeMicro * 0.0001); + + double deltaTimeScrollMultiplier = deltaTimeMicro * 0.00004; + if(scrollSpeed > 0.0) + { + scrollSpeed -= deltaTimeScrollMultiplier; + } + else + { + scrollSpeed += deltaTimeScrollMultiplier; + } + + if(abs(scrollSpeed - deltaTimeScrollMultiplier) <= deltaTimeScrollMultiplier) + scrollSpeed = 0.0; double textOverflow = backgroundSize.y - totalHeight; if(scroll > 0.0 || textOverflow > 0.0) @@ -230,6 +245,15 @@ namespace dchat scrollSpeed = 0.0; } + if(scrollToBottom) + { + scrollToBottom = false; + if(textOverflow < 0.0) + scroll = textOverflow; + else + scroll = 0.0; + } + //staticContentTexture.display(); dirty = false; diff --git a/src/main.cpp b/src/main.cpp index 364b2a9..7245b6e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,19 @@ odhtdb::DatabaseNode createDatabaseNodeFromJoinKey(const string &joinKey) return result; } +void channelAddStoredMessage(Channel *channel, odhtdb::DatabaseStorageObject *nodeStorageObject) +{ + User *user = channel->getUserByPublicKey(nodeStorageObject->creatorPublicKey); + if(!user) + { + fprintf(stderr, "Missing user? %s\n", nodeStorageObject->creatorPublicKey.toString().c_str()); + return; + } + + string msg((const char*)nodeStorageObject->decryptedObject.data.data, nodeStorageObject->decryptedObject.data.size); + channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(nodeStorageObject->createdTimestamp).seconds); +} + void channelAddStoredMessages(Channel *channel, const odhtdb::DatabaseStorageObjectList *nodeStorage) { printf("Load %u messages in channel %s\n", nodeStorage->objects.size(), channel->getName().c_str()); @@ -54,14 +67,7 @@ void channelAddStoredMessages(Channel *channel, const odhtdb::DatabaseStorageObj { if(nodeStorageAddedObject->decryptedObject.operation == odhtdb::DatabaseOperation::ADD_DATA) { - User *user = channel->getUserByPublicKey(nodeStorageAddedObject->creatorPublicKey); - if(!user) - { - fprintf(stderr, "Missing user? %s\n", nodeStorageAddedObject->creatorPublicKey.toString().c_str()); - continue; - } - string msg((const char*)nodeStorageAddedObject->decryptedObject.data.data, nodeStorageAddedObject->decryptedObject.data.size); - channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(nodeStorageAddedObject->createdTimestamp).seconds); + channelAddStoredMessage(channel, nodeStorageAddedObject); } } } @@ -75,27 +81,16 @@ int main(int argc, char **argv) boost::filesystem::current_path(parentPath); // Ensures loading of resources works no matter which path we run this executable from */ + const int FRAMERATE_FOCUSED = 200; + const int FRAMERATE_NOT_FOCUSED = 30; + XInitThreads(); sf::RenderWindow window(sf::VideoMode(1920, 1080), "dchat"); window.setVerticalSyncEnabled(false); - window.setFramerateLimit(60); + window.setFramerateLimit(FRAMERATE_FOCUSED); odhtdb::Database database("bootstrap.ring.cx", 4222, Cache::getDchatDir()); - database.setOnCreateNodeCallback([](const odhtdb::DatabaseCreateNodeRequest &request) - { - - }); - - database.setOnAddNodeCallback([](const odhtdb::DatabaseAddNodeRequest &request) - { - - }); - - database.setOnAddUserCallback([](const odhtdb::DatabaseAddUserRequest &request) - { - - }); //Video video(500, 500, "https://www.youtube.com/watch?v=bs0-EX9mJmg"); Cache cache; @@ -108,10 +103,89 @@ int main(int argc, char **argv) odhtdb::Signature::KeyPair *currentUserKeyPair = nullptr; vector localNodeUsers; + vector waitingToJoinChannels; string currentUserName; string currentUserPassword; + recursive_mutex channelMessageMutex; + + database.setOnCreateNodeCallback([&waitingToJoinChannels, &database, &channels, &channelMessageMutex](const odhtdb::DatabaseCreateNodeRequest &request) + { + lock_guard lock(channelMessageMutex); + for(vector::iterator it = waitingToJoinChannels.begin(); it != waitingToJoinChannels.end(); ++it) + { + if(*request.nodeHash == *it->getRequestHash()) + { + User *localUser = new OfflineUser("You"); + Channel *channel = new Channel(request.name, *it, localUser, &database); + ChannelSidePanel::addChannel(channel); + channels.push_back(channel); + Channel::setCurrent(channel); + + User *nodeCreatorUser = new OnlineUser(request.creatorUser); + channel->addUserLocally(nodeCreatorUser); + + waitingToJoinChannels.erase(it); + return; + } + } + }); + + database.setOnAddNodeCallback([&channels, &channelMessageMutex](const odhtdb::DatabaseAddNodeRequest &request) + { + lock_guard lock(channelMessageMutex); + for(Channel *channel : channels) + { + if(*request.nodeHash == *channel->getId()) + { + User *user = channel->getUserByPublicKey(request.creatorUser->getPublicKey()); + if(!user) + { + fprintf(stderr, "Missing user? %s\n", request.creatorUser->getPublicKey().toString().c_str()); + return; + } + + string msg((const char*)request.decryptedData.data, request.decryptedData.size); + channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(request.timestamp).seconds); + return; + } + } + }); + + database.setOnAddUserCallback([¤tUserKeyPair, &channels, &channelMessageMutex](const odhtdb::DatabaseAddUserRequest &request) + { + lock_guard lock(channelMessageMutex); + if(currentUserKeyPair && request.userToAdd->getPublicKey() == currentUserKeyPair->getPublicKey()) + { + printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorUser->getName().c_str()); + return; + } + + for(Channel *channel : channels) + { + if(*request.nodeHash == *channel->getId()) + { + User *userToAdd = channel->getUserByPublicKey(request.userToAdd->getPublicKey()); + if(userToAdd && currentUserKeyPair && request.userToAdd->getPublicKey() == currentUserKeyPair->getPublicKey() && channel->getLocalUser()->type != User::Type::ONLINE) + { + channel->replaceLocalUser(new OnlineUser(request.userToAdd)); + return; + } + + if(userToAdd) + { + fprintf(stderr, "User %s already exists in channel\n", request.userToAdd->getPublicKey().toString().c_str()); + return; + } + + User *newRemoteUser = new OnlineUser(request.userToAdd); + channel->addUserLocally(newRemoteUser); + return; + } + } + }); - Command::add("login", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database, &channels](const vector &args) + // Login to account + Command::add("login", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database, &channels, &channelMessageMutex](const vector &args) { if(args.size() != 2) { @@ -131,6 +205,7 @@ int main(int argc, char **argv) } channels.clear(); + lock_guard lock(channelMessageMutex); for(auto localNodeUser : localNodeUsers) { auto nodeStorage = database.getStorage().getStorage(localNodeUser.nodeHash); @@ -149,7 +224,7 @@ int main(int argc, char **argv) if(nodeUserIt.second != localNodeUser.localUser) { User *newRemoteUser = new OnlineUser(nodeUserIt.second); - channel->addUser(newRemoteUser); + channel->addUserLocally(newRemoteUser); } } @@ -173,8 +248,10 @@ int main(int argc, char **argv) } }); - Command::add("register", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database](const vector &args) + // Register account + Command::add("register", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database, &channelMessageMutex](const vector &args) { + lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command register (username and password), got %u argument(s)\n", args.size()); @@ -206,8 +283,10 @@ int main(int argc, char **argv) currentUserPassword = args[1]; }); - Command::add("cc", [¤tUserKeyPair, ¤tUserName, &database, &channels](const vector &args) + // Create channel + Command::add("cc", [¤tUserKeyPair, ¤tUserName, &database, &channels, &channelMessageMutex](const vector &args) { + lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command cc (channel name), got %u argument(s)\n", args.size()); @@ -232,8 +311,53 @@ int main(int argc, char **argv) Channel::setCurrent(channel); }); - Command::add("jc", [¤tUserKeyPair, &database, &localNodeUsers](const vector &args) + // Add user + Command::add("au", [&offlineChannel, &channelMessageMutex](const vector &args) { + lock_guard lock(channelMessageMutex); + if(args.size() != 2) + { + fprintf(stderr, "Expected 2 arguments for command au (user id, group id), got %u argument(s)\n", args.size()); + return; + } + + if(args[0].size() != odhtdb::PUBLIC_KEY_NUM_BYTES * 2) + { + fprintf(stderr, "User id is wrong size. Expected to be %u characters, was %u character(s)\n", odhtdb::PUBLIC_KEY_NUM_BYTES * 2, args[0].size()); + return; + } + + if(args[1].size() != odhtdb::GROUP_ID_LENGTH * 2) + { + fprintf(stderr, "Group id is wrong size. Expected to be %u characters, was %u character(s)\n", odhtdb::GROUP_ID_LENGTH * 2, args[1].size()); + return; + } + + Channel *currentChannel = Channel::getCurrent(); + if(currentChannel == &offlineChannel) + { + Channel::getCurrent()->addLocalMessage("You need to be in a channel to add user to the channel", Channel::getCurrent()->getSystemUser()); + return; + } + + auto userIdRaw = odhtdb::hex2bin(args[0].c_str(), args[0].size()); + odhtdb::Signature::PublicKey userPublicKey(userIdRaw.data(), userIdRaw.size()); + + auto groupIdRaw = odhtdb::hex2bin(args[1].c_str(), args[1].size()); + bool userAddResult = currentChannel->addUser(userPublicKey, groupIdRaw); + if(userAddResult) + { + printf("Added user to your channel!\n"); + } + else + { + fprintf(stderr, "Failed to add user to your channel!\n"); + } + }); + + Command::add("jc", [¤tUserKeyPair, &database, &localNodeUsers, &channelMessageMutex](const vector &args) + { + lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command jc (channel join key), got %u argument(s)\n", args.size()); @@ -261,28 +385,15 @@ int main(int argc, char **argv) return; } } -#if 0 - User *newLocalUser = new OnlineUser(localNodeUser.localUser); - odhtdb::DatabaseNode databaseNode(nodeDecryptionKeyResult.second, make_shared(localNodeUser.nodeHash)); - Channel *channel = new Channel(nodeStorage->nodeName, databaseNode, newLocalUser, &database); - - auto nodeUserMapByPublicKey = database.getStorage().getNodeUsers(localNodeUser.nodeHash); - for(auto nodeUserIt : *nodeUserMapByPublicKey) - { - if(nodeUserIt.second != localNodeUser.localUser) - { - User *newRemoteUser = new OnlineUser(nodeUserIt.second); - channel->addUser(newRemoteUser); - } - } - ChannelSidePanel::addChannel(channel); - channels.push_back(channel); - Channel::setCurrent(channel); - channelAddStoredMessages(channel, nodeStorage); -#endif + database.seed(databaseNode); + // TODO: Add the channel to join to a pending join list in a file and remove from it when we have joined the channel. + // The reason for doing that is so if we crash or lose internet connection before we have got `create node` request from remote peers, + // then we need to start seeding again when we login. Once we have `create node` request, then it's added to local cache and when you login, + // it will be used to seed the channel. }); + // Scale UI Command::add("scale", [](const vector &args) { if(args.size() != 1) @@ -296,9 +407,31 @@ int main(int argc, char **argv) printf("UI scaling set to %f\n", scaling); }); + // Get username and id (public key) + Command::add("whoami", [¤tUserKeyPair, ¤tUserName](const vector &args) + { + if(!currentUserKeyPair) + { + Channel::getCurrent()->addLocalMessage("You are not logged in", Channel::getCurrent()->getSystemUser()); + return; + } + + string response = "Username: "; + response += currentUserName; + response += ", id: "; + response += currentUserKeyPair->getPublicKey().toString(); + printf("%s\n", response.c_str()); + Channel::getCurrent()->addLocalMessage(response, Channel::getCurrent()->getSystemUser()); + }); + sf::Event event; while (window.isOpen()) { + channelMessageMutex.lock(); + Channel *currentChannel = Channel::getCurrent(); + bool waitingToJoin = currentChannel != &offlineChannel && currentChannel->getLocalUser()->type != User::Type::ONLINE; + channelMessageMutex.unlock(); + while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) @@ -316,14 +449,34 @@ int main(int argc, char **argv) sf::View view(viewRect); window.setView(view); } - Channel::getCurrent()->processEvent(event); + else if(event.type == sf::Event::GainedFocus) + window.setFramerateLimit(FRAMERATE_FOCUSED); + else if(event.type == sf::Event::LostFocus) + window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); + currentChannel->processEvent(event); } window.clear(ColorScheme::getBackgroundColor()); ChannelSidePanel::draw(window); - Channel::getCurrent()->draw(window, cache); + currentChannel->draw(window, cache); UsersSidePanel::draw(window); ChannelTopPanel::draw(window); + + if(waitingToJoin) + { + auto windowSize = window.getSize(); + + sf::RectangleShape shadeRect(sf::Vector2f(windowSize.x, windowSize.y)); + shadeRect.setFillColor(sf::Color(0, 0, 0, 200)); + window.draw(shadeRect); + + const sf::Font *FONT = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); + const float FONT_SIZE = 30 * Settings::getScaling(); + sf::Text text("Wait until you are added to the channel", *FONT, FONT_SIZE); + text.setPosition(floor((float)windowSize.x * 0.5f - text.getLocalBounds().width * 0.5f), floor((float)windowSize.y * 0.5f - FONT->getLineSpacing(FONT_SIZE) * 0.5f)); + window.draw(text); + } + //video.draw(window); window.display(); } -- cgit v1.2.3