aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules10
-rw-r--r--README.md20
-rw-r--r--bridge/BridgeService.cpp41
-rw-r--r--bridge/BridgeService.hpp41
-rw-r--r--bridge/DiscordService.cpp43
-rw-r--r--bridge/DiscordService.hpp28
m---------depends/dchat_core0
m---------depends/odhtdb0
-rw-r--r--include/Cache.hpp91
-rw-r--r--include/Channel.hpp103
-rw-r--r--include/Chatbar.hpp13
-rw-r--r--include/FileUtil.hpp2
-rw-r--r--include/Gif.hpp51
-rw-r--r--include/ImagePreview.hpp1
-rw-r--r--include/Message.hpp27
-rw-r--r--include/MessageBoard.hpp40
-rw-r--r--include/ResourceCache.hpp3
-rw-r--r--include/Room.hpp15
-rw-r--r--include/RoomContainer.hpp26
-rw-r--r--include/RoomSidePanel.hpp (renamed from include/ChannelSidePanel.hpp)8
-rw-r--r--include/RoomTopPanel.hpp (renamed from include/ChannelTopPanel.hpp)2
-rw-r--r--include/Rpc.hpp21
-rw-r--r--include/StaticImage.hpp17
-rw-r--r--include/StringUtils.hpp18
-rw-r--r--include/StringView.hpp95
-rw-r--r--include/Suggestions.hpp5
-rw-r--r--include/Text.hpp12
-rw-r--r--include/User.hpp92
-rw-r--r--include/UsersSidePanel.hpp5
-rw-r--r--include/WebPagePreview.hpp11
-rw-r--r--project.conf11
-rw-r--r--src/Cache.cpp427
-rw-r--r--src/Channel.cpp405
-rw-r--r--src/Chatbar.cpp50
-rw-r--r--src/Gif.cpp219
-rw-r--r--src/ImagePreview.cpp81
-rw-r--r--src/Message.cpp17
-rw-r--r--src/MessageBoard.cpp180
-rw-r--r--src/ResourceCache.cpp81
-rw-r--r--src/Room.cpp27
-rw-r--r--src/RoomContainer.cpp24
-rw-r--r--src/RoomSidePanel.cpp (renamed from src/ChannelSidePanel.cpp)41
-rw-r--r--src/RoomTopPanel.cpp (renamed from src/ChannelTopPanel.cpp)27
-rw-r--r--src/Rpc.cpp39
-rw-r--r--src/StaticImage.cpp10
-rw-r--r--src/Suggestions.cpp2
-rw-r--r--src/Text.cpp191
-rw-r--r--src/User.cpp93
-rw-r--r--src/UsersSidePanel.cpp92
-rw-r--r--src/WebPagePreview.cpp9
-rw-r--r--src/main.cpp1075
51 files changed, 868 insertions, 3074 deletions
diff --git a/.gitmodules b/.gitmodules
index 76cb6bb..d06accb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,9 @@
[submodule "depends/odhtdb"]
- path = depends/odhtdb
- url = https://gitlab.com/DEC05EBA/odhtdb.git
-[submodule "depends/sibs-pubsub"]
- path = depends/sibs-pubsub
- url = https://gitlab.com/DEC05EBA/sibs-pubsub
+ path = depends/dchat_core
+ url = https://gitlab.com/DEC05EBA/dchat_core.git
[submodule "depends/sibs-functional"]
path = depends/sibs-functional
url = https://gitlab.com/DEC05EBA/sibs-functional.git
+[submodule "depends/dchat_core"]
+ path = depends/dchat_core
+ url = https://gitlab.com/DEC05EBA/dchat_core.git
diff --git a/README.md b/README.md
index de94a95..d916aa6 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,11 @@
# DcHaT
-Decentralized chat using odhtdb. Currently only runs on GNU/Linux.
+Decentralized chat. Currently only runs on GNU/Linux.
Max avatar size is 1mb
# Dependencies
-opendht, boost, libnsgif, mpv, sfml
+boost, libnsgif, mpv, sfml, curl
# Building
-Using [sibs](https://github.com/DEC05EBA/sibs) run `sibs build --release` in the root directory of the project and then run dchat using `./sibs-build/release/dchat`
-## GNU/Linux specific
-xsel, curl
+Using [sibs](https://github.com/DEC05EBA/sibs) run `sibs build --release` in the root directory of the project and then run dchat using `./sibs-build/<platform>/release/dchat`
# TODO
-Use http(s) library to download content (curl?) and stream gif. If content is html, then stop downloading after we have retrieved title, header image and paragraph
-# Channel invite
-Inviting users to channel is done by generating an invite key which is a combination of the node hash and an encryption key (a new encryption key, not the same as the one that is used for the channel nodes themselves).
-After generating the invite key, the user that generated it will listen to requests to join the channel using InfoHash generated by the node hash and encryption key.
-The user that wants to join the channel using the invite key has to sign a message using their public key and encrypt it using the invite encryption key and send it to the same InfoHash the creator of the invite key is using.
-If the creator of the invite key can verify that the message they received was signed with the public key that exists in the message and the message can be decrypted using the invite encryption key, then they will respond with the channel node encryption key.
-The user that created the invite key will then add the user to the channel and the user that wants to join will wait until they receive 'ADD DATA' node that with the public key that they requested to be added with,
-by receiving and decrypting node data with the key the got from the creator of the invite key.
-# Handle incorrect decryption key
-When joining a channel using invite key, we might be given an incorrect decryption key. If that is the case then currently the client crashes
-because decryption fails. This should be gracefully handled
+Use a http(s) library to download content (curl?) and stream gif. If content is html, then stop downloading after we have retrieved title, header image and paragraph
# Screenshot
![Sample screenshot](screenshot.png)
diff --git a/bridge/BridgeService.cpp b/bridge/BridgeService.cpp
deleted file mode 100644
index 0b52643..0000000
--- a/bridge/BridgeService.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "BridgeService.hpp"
-
-namespace dchat
-{
- BridgeServiceUser::BridgeServiceUser(Type _type, const std::string &_name) :
- User(User::Type::OTHER),
- type(_type),
- name(_name)
- {
-
- }
-
- BridgeService::BridgeService()
- {
-
- }
-
- BridgeService::~BridgeService()
- {
- for(User *user : users)
- {
- delete user;
- }
- }
-
- bool BridgeService::addUser(BridgeServiceUser *user)
- {
- for(User *existingUser : users)
- {
- if(static_cast<BridgeServiceUser*>(existingUser)->equals(user))
- return false;
- }
- users.push_back(user);
- return true;
- }
-
- const std::vector<User*>& BridgeService::getUsers() const
- {
- return users;
- }
-} \ No newline at end of file
diff --git a/bridge/BridgeService.hpp b/bridge/BridgeService.hpp
deleted file mode 100644
index e536c81..0000000
--- a/bridge/BridgeService.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include <vector>
-#include <string>
-#include "../include/User.hpp"
-
-namespace dchat
-{
- class BridgeServiceUser : public User
- {
- public:
- enum class Type
- {
- DISCORD
- };
-
- BridgeServiceUser(Type type, const std::string &name);
- virtual ~BridgeServiceUser(){}
-
- virtual const std::string& getName() const override { return name; }
- virtual bool isConnected(i64 timestampUtcSec) const override { return true; }
-
- virtual bool equals(BridgeServiceUser *other) const = 0;
-
- const Type type;
- private:
- std::string name;
- };
-
- class BridgeService
- {
- public:
- BridgeService();
- virtual ~BridgeService();
-
- bool addUser(BridgeServiceUser *user);
- const std::vector<User*>& getUsers() const;
- protected:
- std::vector<User*> users;
- };
-} \ No newline at end of file
diff --git a/bridge/DiscordService.cpp b/bridge/DiscordService.cpp
deleted file mode 100644
index a4546a8..0000000
--- a/bridge/DiscordService.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include "DiscordService.hpp"
-
-namespace dchat
-{
- DiscordServiceUser::DiscordServiceUser(const std::string &name, const u64 _id, bool _connected) :
- BridgeServiceUser(Type::DISCORD, name),
- id(_id),
- connected(_connected)
- {
-
- }
-
- bool DiscordServiceUser::equals(BridgeServiceUser *other) const
- {
- return other && type == other->type && id == static_cast<DiscordServiceUser*>(other)->id;
- }
-
- bool DiscordService::removeUser(u64 discordUserId)
- {
- for(auto it = users.begin(), end = users.end(); it != end; ++it)
- {
- BridgeServiceUser *serviceUser = static_cast<BridgeServiceUser*>(*it);
- if(serviceUser->type == BridgeServiceUser::Type::DISCORD && static_cast<DiscordServiceUser*>(serviceUser)->id == discordUserId)
- {
- delete serviceUser;
- users.erase(it);
- return true;
- }
- }
- return false;
- }
-
- DiscordServiceUser* DiscordService::getUserById(u64 discordUserId)
- {
- for(User *user : users)
- {
- BridgeServiceUser *serviceUser = static_cast<BridgeServiceUser*>(user);
- if(serviceUser->type == BridgeServiceUser::Type::DISCORD && static_cast<DiscordServiceUser*>(serviceUser)->id == discordUserId)
- return static_cast<DiscordServiceUser*>(serviceUser);
- }
- return nullptr;
- }
-} \ No newline at end of file
diff --git a/bridge/DiscordService.hpp b/bridge/DiscordService.hpp
deleted file mode 100644
index c778c41..0000000
--- a/bridge/DiscordService.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-#include "BridgeService.hpp"
-
-namespace dchat
-{
- class DiscordServiceUser : public BridgeServiceUser
- {
- public:
- DiscordServiceUser(const std::string &name, const u64 id, bool connected);
- virtual ~DiscordServiceUser(){}
-
- virtual bool isConnected(i64 timestampUtcSec) const override { return connected; }
- bool equals(BridgeServiceUser *other) const override;
-
- const u64 id;
- bool connected;
- };
-
- class DiscordService : public BridgeService
- {
- public:
- virtual ~DiscordService(){}
-
- bool removeUser(u64 discordUserId);
- DiscordServiceUser* getUserById(u64 discordUserId);
- };
-} \ No newline at end of file
diff --git a/depends/dchat_core b/depends/dchat_core
new file mode 160000
+Subproject 7a21d5056f72f796b47c39dd943745a5f5c3047
diff --git a/depends/odhtdb b/depends/odhtdb
deleted file mode 160000
-Subproject e673a8de53278814a860e9eba03ef776be0a416
diff --git a/include/Cache.hpp b/include/Cache.hpp
deleted file mode 100644
index fc702a7..0000000
--- a/include/Cache.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-#pragma once
-
-#include "types.hpp"
-#include <boost/filesystem/path.hpp>
-#include <SFML/Graphics/Texture.hpp>
-#include <string>
-#include <unordered_map>
-#include <thread>
-#include <mutex>
-#include <vector>
-#include <chrono>
-
-namespace TinyProcessLib
-{
- class Process;
-}
-
-namespace dchat
-{
- class Gif;
- class WebPagePreview;
-
- struct ContentByUrlResult
- {
- enum class Type
- {
- CACHED,
- DOWNLOADING,
- FAILED_DOWNLOAD
- };
-
- enum class CachedType
- {
- NONE,
- TEXTURE,
- GIF,
- WEB_PAGE_PREVIEW
- };
-
- ContentByUrlResult() : texture(nullptr), type(Type::DOWNLOADING), cachedType(CachedType::NONE) {}
- ContentByUrlResult(sf::Texture *_texture, Type _type) : texture(_texture), type(_type), cachedType(CachedType::TEXTURE) {}
- ContentByUrlResult(Gif *_gif, Type _type) : gif(_gif), type(_type), cachedType(CachedType::GIF) {}
- ContentByUrlResult(WebPagePreview *_webPagePreview, Type _type) : webPagePreview(_webPagePreview), type(_type), cachedType(CachedType::WEB_PAGE_PREVIEW) {}
-
- // @texture is null if @type is DOWNLOADING or FAILED_DOWNLOAD
- union
- {
- sf::Texture *texture;
- Gif *gif;
- WebPagePreview *webPagePreview;
- };
-
- Type type;
- CachedType cachedType;
- i64 lastAccessed;
- };
-
- class Cache
- {
- public:
- Cache();
- ~Cache();
-
- // Creates directory if it doesn't exist (recursively). Throws boost exception on failure
- static boost::filesystem::path getDchatDir();
-
- // Creates directory if it doesn't exist (recursively). Throws boost exception on failure
- static boost::filesystem::path getImagesDir();
-
- static void loadBindsFromFile();
- static void replaceBindsInFile(const std::unordered_map<std::string, std::string> &binds);
-
- // Get cached content or download it.
- // Default download file limit is 12MB
- // Returns ContentByUrlResult describing texture status.
- const ContentByUrlResult getContentByUrl(const std::string &url, int downloadLimitBytes = 12582912);
- private:
- struct ImageDownloadInfo
- {
- TinyProcessLib::Process *process;
- std::string url;
- };
-
- std::thread downloadWaitThread;
- std::thread checkContentAccessTimeThread;
- std::vector<ImageDownloadInfo> imageDownloadProcesses;
- std::vector<ImageDownloadInfo> imageDownloadProcessesQueue;
- std::mutex imageDownloadMutex;
- bool alive;
- };
-}
diff --git a/include/Channel.hpp b/include/Channel.hpp
deleted file mode 100644
index 58bbcc3..0000000
--- a/include/Channel.hpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#pragma once
-
-#include "MessageBoard.hpp"
-#include "Chatbar.hpp"
-#include "User.hpp"
-#include "Channel.hpp"
-#include "types.hpp"
-#include "Suggestions.hpp"
-#include "../bridge/DiscordService.hpp"
-#include <vector>
-#include <unordered_map>
-#include <SFML/System/Clock.hpp>
-#include <odhtdb/DatabaseNode.hpp>
-#include <odhtdb/Signature.hpp>
-#include <odhtdb/Group.hpp>
-#include <odhtdb/Hash.hpp>
-#include <odhtdb/DhtKey.hpp>
-#include <odhtdb/Database.hpp>
-
-
-namespace dchat
-{
- enum class ChannelDataType : u8
- {
- ADD_MESSAGE,
- EDIT_MESSAGE,
- DELETE_MESSAGE,
- NICKNAME_CHANGE,
- CHANGE_AVATAR,
- CHANGE_CHANNEL_NAME,
- };
-
- class Channel
- {
- public:
- Channel(const std::string &name, const odhtdb::DatabaseNode &databaseNodeInfo = odhtdb::DatabaseNode(), User *localUser = nullptr, std::shared_ptr<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<User*>& getUsers() const;
- OnlineUser* getUserByPublicKey(const odhtdb::Signature::PublicKey &publicKey);
- const odhtdb::DatabaseNode& getNodeInfo() const;
- Message* getLatestMessage();
-
- // If timestamp is 0, then current time is used
- void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds = 0);
- void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id);
- void addLocalDiscordMessage(const std::string &discordUserName, u64 discordUserId, const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id);
- void addSystemMessage(const std::string &msg, bool plainText = true);
- void addMessage(const std::string &msg);
- void deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser);
- void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser);
-
- void addUserLocally(User *user);
- bool addUser(const odhtdb::Signature::PublicKey &userId, const odhtdb::DataView &groupId);
- void replaceLocalUser(OnlineLocalUser *newOnlineLocalUser);
- void changeNick(const std::string &newNick);
- void setAvatar(const std::string &newAvatarUrl);
- void setNameLocally(const std::string &name);
- void setName(const std::string &name);
-
- // Returns -1 on failure
- int getUserLowestPermissionLevel(OnlineUser *user) const;
-
- void processEvent(const sf::Event &event, Cache &cache);
- void draw(sf::RenderWindow &window, Cache &cache);
-
- void update();
- // Returns 0 if we are offline
- u32 getSyncedTimestampUtcInSec();
-
- const std::vector<BridgeService*>& getBridgeServices() const;
- DiscordService* getDiscordService();
-
- static void setCurrent(Channel *channel);
- static Channel* getCurrent();
- private:
- void sendPing(u32 pingTimestampSec);
- protected:
- std::shared_ptr<odhtdb::Database> database;
- odhtdb::DatabaseNode databaseNodeInfo;
- std::string name;
- MessageBoard messageBoard;
- Chatbar chatbar;
- Suggestions suggestions;
- User *localUser;
- SystemUser systemUser;
- std::vector<User*> users;
- std::unordered_map<u64, OnlineDiscordUser*> discordUserById;
- odhtdb::Signature::MapPublicKey<OnlineUser*> publicKeyOnlineUsersMap;
- odhtdb::InfoHash pingKey;
- sibs::ListenHandle pingListener;
- sf::Clock pingTimer;
-
- std::vector<BridgeService*> bridgeServices;
- };
-}
diff --git a/include/Chatbar.hpp b/include/Chatbar.hpp
index 10bbebd..701220a 100644
--- a/include/Chatbar.hpp
+++ b/include/Chatbar.hpp
@@ -1,18 +1,20 @@
#pragma once
-#include "StringView.hpp"
#include "Text.hpp"
-#include "Cache.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/System/Clock.hpp>
+#include <dchat/StringView.hpp>
#include <string>
#include <unordered_map>
+#include <memory>
namespace dchat
{
- class Channel;
+ class Cache;
+ class Room;
+ class MessageBoard;
class Chatbar
{
@@ -21,8 +23,8 @@ namespace dchat
bool isFocused() const;
- void processEvent(const sf::Event &event, Cache &cache, Channel *channel);
- void draw(sf::RenderWindow &window, Cache &cache);
+ void processEvent(const sf::Event &event, Cache *cache, std::shared_ptr<Room> room, MessageBoard *messageBoard);
+ void draw(sf::RenderWindow &window, Cache *cache);
static sf::Vector2f getInputPosition(sf::RenderWindow &window);
static sf::Vector2f getInputSize(sf::RenderWindow &window);
@@ -30,6 +32,7 @@ namespace dchat
static bool addBind(const std::string &key, const std::string &value, bool updateFile = true);
static bool removeBind(const std::string &key, bool updateFile = true);
+ static void loadBindsFromFile();
static const std::unordered_map<std::string, std::string>& getBinds();
private:
void processChatCommand(const StringView &cmd);
diff --git a/include/FileUtil.hpp b/include/FileUtil.hpp
index 097b607..420ed72 100644
--- a/include/FileUtil.hpp
+++ b/include/FileUtil.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "StringView.hpp"
+#include <dchat/StringView.hpp>
#include <boost/filesystem/path.hpp>
#include <stdexcept>
diff --git a/include/Gif.hpp b/include/Gif.hpp
index ebcb5d4..6edef92 100644
--- a/include/Gif.hpp
+++ b/include/Gif.hpp
@@ -1,52 +1,19 @@
#pragma once
-#include "StringView.hpp"
-#include <SFML/Graphics/RenderTarget.hpp>
+#include <dchat/Gif.hpp>
#include <SFML/Graphics/Texture.hpp>
-#include <SFML/Graphics/Sprite.hpp>
-#include <SFML/System/Clock.hpp>
-#include <boost/filesystem/path.hpp>
-#include <stdexcept>
-extern "C"
-{
-#include <libnsgif.h>
-}
namespace dchat
{
- class GifLoadException : public std::runtime_error
- {
- public:
- GifLoadException(const std::string &errMsg) : std::runtime_error(errMsg) {}
- };
-
- class Gif
+ class SfmlGif : public Gif
{
public:
- // Throws GifLoadException on error
- Gif(const boost::filesystem::path &filepath);
- Gif(StringView &&fileContent);
- ~Gif();
-
- sf::Vector2u getSize() const;
-
- void setPosition(const sf::Vector2f &position);
- sf::Vector2f getPosition() const;
-
- void setScale(const sf::Vector2f &scale);
- void setColor(sf::Color color);
- void draw(sf::RenderTarget &target, const sf::RenderStates &renderStates = sf::RenderStates::Default);
-
- static bool isDataGif(const StringView &data);
- private:
- void init();
- private:
- gif_animation gif;
- StringView fileContent;
- unsigned int currentFrame;
- sf::Sprite sprite;
+ SfmlGif(StringView fileContent);
+ virtual ~SfmlGif(){}
+
+ bool createTexture() override;
+ void updateTexture(void *textureData) override;
+
sf::Texture texture;
- double timeElapsedCs;
- sf::Clock frameTimer;
};
-}
+} \ No newline at end of file
diff --git a/include/ImagePreview.hpp b/include/ImagePreview.hpp
index 1d20fe8..0f42465 100644
--- a/include/ImagePreview.hpp
+++ b/include/ImagePreview.hpp
@@ -38,6 +38,7 @@ namespace dchat
sf::Vector2u calculateImageSize(sf::Vector2u windowSize) const;
private:
+ sf::Vector2f position;
sf::Sprite sprite;
sf::Vector2u size;
sf::Clock lastSeenTimer;
diff --git a/include/Message.hpp b/include/Message.hpp
index 2327a0b..0541574 100644
--- a/include/Message.hpp
+++ b/include/Message.hpp
@@ -1,29 +1,20 @@
#pragma once
-#include "User.hpp"
+#include <memory>
#include "Text.hpp"
-#include <string>
-#include <vector>
-#include <odhtdb/Hash.hpp>
namespace dchat
{
+ struct RoomMessage;
+
class Message
{
public:
- enum class Type
- {
- REGULAR,
- EDITED
- };
-
- // If timestamp is 0, then timestamp is not used
- Message(User *user, const std::string &text, u64 timestampSeconds = 0, bool plainText = false);
-
- const User *user;
+ Message(std::shared_ptr<RoomMessage> roomMessage, bool plainText);
+
+ std::shared_ptr<RoomMessage> roomMessage;
Text text;
- const u64 timestampSeconds;
- Type type;
- odhtdb::Hash id;
+
+ bool onlineUser;
};
-}
+} \ No newline at end of file
diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp
index 1d1f523..c1eecb0 100644
--- a/include/MessageBoard.hpp
+++ b/include/MessageBoard.hpp
@@ -1,9 +1,8 @@
#pragma once
-#include "Message.hpp"
#include "types.hpp"
-#include "../include/Cache.hpp"
-#include "../include/Scrollbar.hpp"
+#include "Message.hpp"
+#include "Scrollbar.hpp"
#include <SFML/Graphics/RenderTexture.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Window/Event.hpp>
@@ -11,37 +10,41 @@
#include <odhtdb/Signature.hpp>
#include <stdexcept>
#include <mutex>
+#include <memory>
namespace dchat
{
- class Channel;
+ class Room;
+ struct RoomMessage;
+ class Cache;
+ class User;
class MessageBoard
{
- friend class Channel;
public:
- MessageBoard(Channel *channel);
+ MessageBoard(std::shared_ptr<Room> room);
~MessageBoard();
- void processEvent(const sf::Event &event, Cache &cache);
- void draw(sf::RenderWindow &window, Cache &cache);
-
- Message* getLatestMessage();
- const std::vector<Message*>& getMessages() const;
+ void processEvent(const sf::Event &event, Cache *cache);
+ void draw(sf::RenderWindow &window, Cache *cache);
+
+ bool addMessage(Message *message);
+ void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser);
+
+ void addOfflineUserMessage(std::string msg, bool plainText);
+ void addSystemUserMessage(std::string msg, bool plainText);
private:
usize findPositionToInsertMessageByTimestamp(Message *message);
void updateStaticContentTexture(const sf::Vector2u &newSize);
- bool addMessage(Message *message, const odhtdb::Hash &id);
- void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser);
- void drawDefault(sf::RenderWindow &window, Cache &cache);
- void drawSimple(sf::RenderWindow &window, Cache &cache);
+ void drawDefault(sf::RenderWindow &window, Cache *cache);
+ void drawSimple(sf::RenderWindow &window, Cache *cache);
private:
- Channel *channel;
+ std::shared_ptr<Room> room;
bool dirty;
- std::vector<Message*> messages;
odhtdb::MapHash<Message*> messageIdMap;
+ std::vector<Message*> messages;
double scroll;
double scrollSpeed;
double totalHeight;
@@ -54,5 +57,8 @@ namespace dchat
usize visibleMessageStartIndex;
usize visibleMessageEndIndex;
Scrollbar scrollbar;
+
+ std::shared_ptr<User> offlineUser;
+ std::shared_ptr<User> systemUser;
};
}
diff --git a/include/ResourceCache.hpp b/include/ResourceCache.hpp
index 75ebd88..ab7a921 100644
--- a/include/ResourceCache.hpp
+++ b/include/ResourceCache.hpp
@@ -3,6 +3,7 @@
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Shader.hpp>
+#include <dchat/Cache.hpp>
#include <string>
#include <stdexcept>
@@ -24,5 +25,7 @@ namespace dchat
static sf::Texture* getTexture(const std::string &filepath);
static sf::Shader* getShader(const std::string &filepath, sf::Shader::Type shaderType);
+
+ static Cache* getCache();
};
}
diff --git a/include/Room.hpp b/include/Room.hpp
new file mode 100644
index 0000000..bcdadc0
--- /dev/null
+++ b/include/Room.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <memory>
+
+namespace dchat
+{
+ class Room;
+ class Rooms;
+
+ std::shared_ptr<Room> getCurrentRoom();
+ void setCurrentRoom(std::shared_ptr<Room> room);
+
+ std::shared_ptr<Rooms> getRooms();
+ void setRooms(std::shared_ptr<Rooms> rooms);
+} \ No newline at end of file
diff --git a/include/RoomContainer.hpp b/include/RoomContainer.hpp
new file mode 100644
index 0000000..fc3e8ae
--- /dev/null
+++ b/include/RoomContainer.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "MessageBoard.hpp"
+#include "Chatbar.hpp"
+#include <SFML/Graphics/RenderWindow.hpp>
+#include <memory>
+
+namespace dchat
+{
+ class Room;
+ class Cache;
+
+ class RoomContainer
+ {
+ public:
+ RoomContainer(std::shared_ptr<Room> room);
+
+ void processEvent(const sf::Event &event, Cache *cache);
+ void draw(sf::RenderWindow &window, Cache *cache);
+
+ std::shared_ptr<Room> room;
+ MessageBoard messageBoard;
+ Chatbar chatbar;
+ bool offlineRoom;
+ };
+} \ No newline at end of file
diff --git a/include/ChannelSidePanel.hpp b/include/RoomSidePanel.hpp
index 265ee67..95000ba 100644
--- a/include/ChannelSidePanel.hpp
+++ b/include/RoomSidePanel.hpp
@@ -1,17 +1,13 @@
#pragma once
#include <SFML/Graphics/RenderWindow.hpp>
+#include <memory>
namespace dchat
{
- class Channel;
-
- class ChannelSidePanel
+ class RoomSidePanel
{
public:
- static void addChannel(Channel *channel);
- static void removeAllChannels();
-
static void draw(sf::RenderWindow &window);
static float getWidth();
static float getHeight();
diff --git a/include/ChannelTopPanel.hpp b/include/RoomTopPanel.hpp
index 41fe9a2..1787753 100644
--- a/include/ChannelTopPanel.hpp
+++ b/include/RoomTopPanel.hpp
@@ -4,7 +4,7 @@
namespace dchat
{
- class ChannelTopPanel
+ class RoomTopPanel
{
public:
static void draw(sf::RenderWindow &window);
diff --git a/include/Rpc.hpp b/include/Rpc.hpp
deleted file mode 100644
index c8b47e0..0000000
--- a/include/Rpc.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include <zmq.hpp>
-#include "types.hpp"
-#include <functional>
-
-namespace dchat
-{
- using RpcRecvCallbackFunc = std::function<void(zmq::message_t*)>;
-
- class Rpc
- {
- public:
- Rpc(u16 port);
- void recv(RpcRecvCallbackFunc recvCallbackFunc);
- bool send(const void *data, const usize size);
- private:
- zmq::context_t context;
- zmq::socket_t socket;
- };
-} \ No newline at end of file
diff --git a/include/StaticImage.hpp b/include/StaticImage.hpp
new file mode 100644
index 0000000..3ede61c
--- /dev/null
+++ b/include/StaticImage.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <dchat/StaticImage.hpp>
+#include <dchat/StringView.hpp>
+#include <SFML/Graphics/Texture.hpp>
+
+namespace dchat
+{
+ class SfmlStaticImage : public StaticImage
+ {
+ public:
+ SfmlStaticImage(const boost::filesystem::path &path);
+ virtual ~SfmlStaticImage(){}
+
+ sf::Texture texture;
+ };
+} \ No newline at end of file
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp
deleted file mode 100644
index 6b237dd..0000000
--- a/include/StringUtils.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-#include <string>
-
-namespace dchat
-{
- static std::string stringReplaceChar(const std::string &str, const std::string &from, const std::string &to)
- {
- std::string result = str;
- size_t pos = 0;
- while((pos = result.find(from, pos)) != std::string::npos)
- {
- result.replace(pos, from.size(), to);
- pos += to.size();
- }
- return result;
- }
-}
diff --git a/include/StringView.hpp b/include/StringView.hpp
deleted file mode 100644
index d320a85..0000000
--- a/include/StringView.hpp
+++ /dev/null
@@ -1,95 +0,0 @@
-#pragma once
-
-#include "types.hpp"
-#include <cstring>
-#include <cassert>
-
-namespace dchat
-{
- template <typename CharType>
- class BasicStringView
- {
- public:
- BasicStringView() : data(nullptr), size(0)
- {
-
- }
-
- BasicStringView(const BasicStringView<CharType> &other) : data(other.data), size(other.size)
- {
-
- }
-
- BasicStringView(const CharType *_data) : data(_data), size(strlen(_data))
- {
-
- }
-
- BasicStringView(const CharType *_data, usize _size) : data(_data), size(_size)
- {
-
- }
-
- BasicStringView<CharType>& operator = (const BasicStringView<CharType> &other)
- {
- data = other.data;
- size = other.size;
- return *this;
- }
-
- BasicStringView(BasicStringView<CharType> &&other)
- {
- data = other.data;
- size = other.size;
-
- other.data = nullptr;
- other.size = 0;
- }
-
- bool equals(const BasicStringView<CharType> &other) const
- {
- if(size != other.size) return false;
- return memcmp(data, other.data, size * sizeof(CharType)) == 0;
- }
-
- bool operator == (const BasicStringView<CharType> &other) const
- {
- return equals(other);
- }
-
- bool operator != (const BasicStringView<CharType> &other) const
- {
- return !equals(other);
- }
-
- CharType operator [] (usize index) const
- {
- assert(index < size);
- return data[index];
- }
-
- // Returns -1 if substr not found.
- // TODO: Make this more efficient
- usize find(const BasicStringView<CharType> &substr, usize offset = 0) const
- {
- if(substr.size == 0)
- return -1;
-
- if(offset + substr.size > size)
- return -1;
-
- for(usize i = offset; i < size - (substr.size - 1); ++i)
- {
- if(memcmp(data + i, substr.data, substr.size * sizeof(CharType)) == 0)
- return i;
- }
- return -1;
- }
-
- const CharType *data;
- usize size;
- };
-
- using StringView = BasicStringView<char>;
- using StringViewUtf32 = BasicStringView<u32>;
-}
diff --git a/include/Suggestions.hpp b/include/Suggestions.hpp
index 56f3afa..b2d19f9 100644
--- a/include/Suggestions.hpp
+++ b/include/Suggestions.hpp
@@ -1,18 +1,19 @@
#pragma once
#include "Text.hpp"
-#include "Cache.hpp"
#include <vector>
#include <string>
#include <SFML/Graphics/RenderWindow.hpp>
namespace dchat
{
+ class Cache;
+
class Suggestions
{
public:
void show(const std::vector<std::string> &texts);
- void draw(sf::RenderWindow &window, Cache &cache);
+ void draw(sf::RenderWindow &window, Cache *cache);
private:
std::vector<std::unique_ptr<Text>> texts;
};
diff --git a/include/Text.hpp b/include/Text.hpp
index 24ea02a..86814a3 100644
--- a/include/Text.hpp
+++ b/include/Text.hpp
@@ -1,17 +1,18 @@
#pragma once
-#include "StringView.hpp"
-#include "Cache.hpp"
#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/System/String.hpp>
#include <SFML/System/Clock.hpp>
+#include <dchat/StringView.hpp>
#include <vector>
namespace dchat
{
+ class Cache;
+
struct TextElement
{
enum class Type
@@ -59,13 +60,13 @@ namespace dchat
// Warning: won't update until @draw is called
float getHeight() const;
- void processEvent(const sf::Event &event, Cache &cache);
+ void processEvent(const sf::Event &event, Cache *cache);
// Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible.
// Returns true if text was drawn on screen (if text is within window borders)
- bool draw(sf::RenderTarget &target, Cache &cache);
+ bool draw(sf::RenderTarget &target, Cache *cache);
private:
- void onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache);
+ void onMouseClick(const sf::Event::MouseButtonEvent &event, Cache *cache);
private:
enum class CaretMoveDirection : u8
{
@@ -110,5 +111,6 @@ namespace dchat
int caretIndex;
sf::Vector2f caretPosition;
sf::Clock lastSeenTimer;
+ sf::Vector2u renderTargetSize;
};
}
diff --git a/include/User.hpp b/include/User.hpp
deleted file mode 100644
index 7e99c60..0000000
--- a/include/User.hpp
+++ /dev/null
@@ -1,92 +0,0 @@
-#pragma once
-
-#include "types.hpp"
-#include <string>
-#include <odhtdb/Signature.hpp>
-
-namespace dchat
-{
- class User
- {
- public:
- enum class Type
- {
- OTHER,
- ONLINE_REMOTE_USER,
- ONLINE_LOCAL_USER,
- ONLINE_DISCORD_USER,
- OFFLINE,
- SYSTEM
- };
-
- User(Type type);
- virtual ~User(){}
- virtual const std::string& getName() const = 0;
- virtual bool isOnlineUser() const { return false; }
-
- virtual bool isConnected(i64 timestampUtcSec) const { return true; }
-
- const Type type;
- std::string avatarUrl;
- };
-
- class OnlineUser : public User
- {
- public:
- OnlineUser(const std::string &name, Type type);
- virtual ~OnlineUser(){}
-
- virtual const std::string& getName() const override;
- virtual const odhtdb::Signature::PublicKey& getPublicKey() const = 0;
-
- bool isConnected(i64 timestampUtcSec) const override;
- bool isOnlineUser() const override { return true; }
-
- std::string name;
- u32 pingTimestampSec;
- };
-
- class OnlineRemoteUser : public OnlineUser
- {
- public:
- OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &publicKey);
- virtual const odhtdb::Signature::PublicKey& getPublicKey() const override;
-
- const odhtdb::Signature::PublicKey publicKey;
- };
-
- class OnlineLocalUser : public OnlineUser
- {
- public:
- OnlineLocalUser(const std::string &name, const odhtdb::Signature::KeyPair &keyPair);
- virtual const odhtdb::Signature::PublicKey& getPublicKey() const override;
-
- const odhtdb::Signature::KeyPair keyPair;
- };
-
- class OnlineDiscordUser : public OnlineUser
- {
- public:
- OnlineDiscordUser(const std::string &discordUserName, u64 discordUserId, User *bridgeOwner);
- virtual const odhtdb::Signature::PublicKey& getPublicKey() const override;
-
- u64 discordUserId;
- User *bridgeOwner;
- };
-
- class OfflineUser : public User
- {
- public:
- OfflineUser(const std::string &name);
- virtual const std::string& getName() const override;
-
- const std::string name;
- };
-
- class SystemUser : public User
- {
- public:
- SystemUser();
- virtual const std::string& getName() const override;
- };
-}
diff --git a/include/UsersSidePanel.hpp b/include/UsersSidePanel.hpp
index 9e48ee6..b76eb5e 100644
--- a/include/UsersSidePanel.hpp
+++ b/include/UsersSidePanel.hpp
@@ -1,16 +1,15 @@
#pragma once
-#include "Cache.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
namespace dchat
{
- class Channel;
+ class Cache;
class UsersSidePanel
{
public:
- static void draw(sf::RenderWindow &window, Cache &cache);
+ static void draw(sf::RenderWindow &window, Cache *cache);
static float getWidth();
};
}
diff --git a/include/WebPagePreview.hpp b/include/WebPagePreview.hpp
index 6f6c0f0..fde653d 100644
--- a/include/WebPagePreview.hpp
+++ b/include/WebPagePreview.hpp
@@ -1,16 +1,17 @@
#pragma once
-#include <SFML/System/String.hpp>
#include "Text.hpp"
+#include <dchat/WebPagePreview.hpp>
+#include <string>
namespace dchat
{
- class WebPagePreview
+ class SfmlWebPagePreview : public WebPagePreview
{
public:
- WebPagePreview(const sf::String &title, const sf::String &description);
-
+ SfmlWebPagePreview(const std::string &title, const std::string &description);
+
Text title;
Text description;
};
-}
+} \ No newline at end of file
diff --git a/project.conf b/project.conf
index 0bcfcce..ca7655c 100644
--- a/project.conf
+++ b/project.conf
@@ -1,8 +1,8 @@
[package]
name = "dchat"
-version = "0.2.0"
+version = "0.3.0"
type = "executable"
-platforms = ["linux", "win"]
+platforms = ["linux"]
[dependencies]
sfml-window = "2.5"
@@ -12,9 +12,4 @@ boost-filesystem = "1.66.0"
tiny-process = "2.0.0"
mpv = "1.25.0"
gl = ">=17.3"
-x11 = "1.6.5"
-libnsgif = "0.2.0"
-libpreview = "0.2.0"
-libzmq = "4.2"
-libgd = "2.2.5"
-msgpack = "3"
+x11 = "1.6.5" \ No newline at end of file
diff --git a/src/Cache.cpp b/src/Cache.cpp
deleted file mode 100644
index 77ba515..0000000
--- a/src/Cache.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-#include "../include/Cache.hpp"
-#include "../include/env.hpp"
-#include "../include/ResourceCache.hpp"
-#include "../include/FileUtil.hpp"
-#include "../include/Gif.hpp"
-#include "../include/Chatbar.hpp"
-#include "../include/WebPagePreview.hpp"
-#include "../include/ImagePreview.hpp"
-#include "../include/StringUtils.hpp"
-#include <boost/filesystem/convenience.hpp>
-#include <unordered_map>
-#include <process.hpp>
-#include <odhtdb/Hash.hpp>
-#include <sibs/SafeSerializer.hpp>
-#include <sibs/SafeDeserializer.hpp>
-#include <libpreview.h>
-#include <gd.h>
-
-#if OS_FAMILY == OS_FAMILY_POSIX
-#include <pwd.h>
-#else
-#include <string>
-#endif
-
-using namespace std;
-using namespace TinyProcessLib;
-
-namespace dchat
-{
- static unordered_map<string, ContentByUrlResult> contentUrlCache;
- const i64 CONTENT_NOT_VISIBLE_AGE_MS = 30000; // Delete content from cache after a specified amount of time if the content is not visible on the screen
-
- static boost::filesystem::path getHomeDir()
- {
- #if OS_FAMILY == OS_FAMILY_POSIX
- const char *homeDir = getenv("HOME");
- if(!homeDir)
- {
- passwd *pw = getpwuid(getuid());
- homeDir = pw->pw_dir;
- }
- return boost::filesystem::path(homeDir);
- #elif OS_FAMILY == OS_FAMILY_WINDOWS
- BOOL ret;
- HANDLE hToken;
- std::wstring homeDir;
- DWORD homeDirLen = MAX_PATH;
- homeDir.resize(homeDirLen);
-
- if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken))
- return Result<FileString>::Err("Failed to open process token");
-
- if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen))
- {
- CloseHandle(hToken);
- return Result<FileString>::Err("Failed to get home directory");
- }
-
- CloseHandle(hToken);
- homeDir.resize(wcslen(homeDir.c_str()));
- return boost::filesystem::path(homeDir);
- #endif
- }
-
- boost::filesystem::path Cache::getDchatDir()
- {
- boost::filesystem::path dchatHomeDir = getHomeDir() / ".local" / "share" / "dchat";
- boost::filesystem::create_directories(dchatHomeDir);
- return dchatHomeDir;
- }
-
- boost::filesystem::path Cache::getImagesDir()
- {
- boost::filesystem::path imagesDir = getDchatDir() / "images";
- boost::filesystem::create_directories(imagesDir);
- return imagesDir;
- }
-
- void Cache::loadBindsFromFile()
- {
- StringView fileContent;
- try
- {
- fileContent = getFileContent(getDchatDir() / "binds");
- sibs::SafeDeserializer deserializer((const u8*)fileContent.data, fileContent.size);
-
- while(!deserializer.empty())
- {
- u8 keySize = deserializer.extract<u8>();
- string key;
- key.resize(keySize);
- deserializer.extract((u8*)&key[0], keySize);
-
- u8 valueSize = deserializer.extract<u8>();
- string value;
- value.resize(valueSize);
- deserializer.extract((u8*)&value[0], valueSize);
-
- Chatbar::addBind(key, value, false);
- }
- }
- catch(FileException &e)
- {
- fprintf(stderr, "Failed to read binds from file, reason: %s\n", e.what());
- }
-
- delete[] fileContent.data;
- }
-
- void Cache::replaceBindsInFile(const unordered_map<string, string> &binds)
- {
- sibs::SafeSerializer serializer;
- for(auto &it : binds)
- {
- serializer.add((u8)it.first.size());
- serializer.add((const u8*)it.first.data(), it.first.size());
-
- serializer.add((u8)it.second.size());
- serializer.add((const u8*)it.second.data(), it.second.size());
- }
- fileReplace(getDchatDir() / "binds", StringView((const char*)serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
-
- static bool downscaleImage(const boost::filesystem::path &filepath, void *data, const int size, const int newWidth, const int newHeight)
- {
- gdImagePtr imgPtr = gdImageCreateFromPngPtr(size, data);
- if(!imgPtr)
- return false;
-
- int width = gdImageSX(imgPtr);
- if(width < newWidth)
- {
- gdImageDestroy(imgPtr);
- return false;
- }
-
- int height = gdImageSX(imgPtr);
- if(height < newHeight)
- {
- gdImageDestroy(imgPtr);
- return false;
- }
-
- gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED);
- gdImagePtr newImgPtr = gdImageScale(imgPtr, newWidth, newHeight);
- if(!newImgPtr)
- {
- gdImageDestroy(imgPtr);
- return false;
- }
-
- bool success = (gdImageFile(newImgPtr, filepath.c_str()) == 0);
- gdImageDestroy(imgPtr);
- gdImageDestroy(newImgPtr);
- return success;
- }
-
- static ContentByUrlResult loadImageFromFile(const boost::filesystem::path &filepath, bool loadFromCache)
- {
- StringView fileContent;
- try
- {
- fileContent = getFileContent(filepath);
-
- sf::String webPageTitle;
- sf::String webPageDescription;
- bool foundHtmlContent = false;
- preview_state state;
- preview_init(&state);
- size_t offset = 0;
- do
- {
- // TODO: Get file content before doing this, the file might be in utf-16 encoding. That can happen for example if file contains html.
- // Content type can be retrieved from HTTP response header when downloading content
- offset += preview_step(&state, fileContent.data + offset, fileContent.size - offset);
- if(state.step_result == PREVIEW_FOUND_IMAGE)
- {
- if(Gif::isDataGif(fileContent))
- {
- Gif *gif = new Gif(move(fileContent));
- return { gif, ContentByUrlResult::Type::CACHED };
- }
- else
- {
- if(!loadFromCache)
- {
- if(!downscaleImage(filepath, (void*)fileContent.data, fileContent.size, 100, 100))
- {
- fprintf(stderr, "Failed to resize image: %s, using original file\n", filepath.c_str());
- }
- }
-
- sf::Texture *texture = new sf::Texture();
- if(texture->loadFromFile(filepath.c_str()))
- {
- delete[] fileContent.data;
- fileContent.data = nullptr;
- texture->setSmooth(true);
- texture->generateMipmap();
- return { texture, ContentByUrlResult::Type::CACHED };
- }
- delete texture;
- }
- break;
- }
- else if(state.step_result == PREVIEW_FOUND_TITLE)
- {
- foundHtmlContent = true;
- webPageTitle = sf::String::fromUtf8(state.meta_content, state.meta_content + state.meta_content_length);
- }
- else if(state.step_result == PREVIEW_FOUND_DESCRIPTION)
- {
- foundHtmlContent = true;
- webPageDescription = sf::String::fromUtf8(state.meta_content, state.meta_content + state.meta_content_length);
- }
- } while(offset < fileContent.size);
-
- delete[] fileContent.data;
- fileContent.data = nullptr;
-
- if(foundHtmlContent)
- {
- // TODO: Use move semantics for webPageTitle and webPageDescription when SFML supports it
- WebPagePreview *webPagePreview = new WebPagePreview(webPageTitle, webPageDescription);
- return { webPagePreview, ContentByUrlResult::Type::CACHED };
- }
- }
- catch(std::exception &e)
- {
- fprintf(stderr, "Failed to load image %s, reason: %s\n", filepath.string().c_str(), e.what());
- }
- return { (sf::Texture*)nullptr, ContentByUrlResult::Type::FAILED_DOWNLOAD };
- }
-
- Cache::Cache() :
- alive(true)
- {
- downloadWaitThread = thread([this]
- {
- while(alive)
- {
- for(vector<ImageDownloadInfo>::iterator it = imageDownloadProcesses.begin(); it != imageDownloadProcesses.end();)
- {
- int exitStatus;
- if(it->process->try_get_exit_status(exitStatus))
- {
- boost::filesystem::path filepath = getImagesDir();
- odhtdb::Hash urlHash(it->url.data(), it->url.size());
- filepath /= urlHash.toString();
-
- ContentByUrlResult contentByUrlResult;
- bool failed = exitStatus != 0;
- if(failed)
- {
- contentByUrlResult = { (sf::Texture*)nullptr, ContentByUrlResult::Type::FAILED_DOWNLOAD };
- }
- else
- {
- contentByUrlResult = loadImageFromFile(filepath, false);
- contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
- if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
- {
- printf("Download content from url: %s\n", it->url.c_str());
- }
- }
-
- imageDownloadMutex.lock();
- contentUrlCache[it->url] = contentByUrlResult;
- boost::filesystem::path downloadingFilepath = filepath;
- downloadingFilepath += ".downloading";
- // Intentionally ignore failure, program should not crash if we fail to remove these files...
- boost::system::error_code err;
- boost::filesystem::remove(downloadingFilepath, err);
- imageDownloadMutex.unlock();
- it = imageDownloadProcesses.erase(it);
- }
- else
- ++it;
- }
-
- while(alive && imageDownloadProcesses.empty() && imageDownloadProcessesQueue.empty())
- this_thread::sleep_for(chrono::milliseconds(20));
-
- if(!imageDownloadProcessesQueue.empty())
- {
- imageDownloadMutex.lock();
- for(auto imageDownloadInfo : imageDownloadProcessesQueue)
- {
- imageDownloadProcesses.push_back(imageDownloadInfo);
- }
- imageDownloadProcessesQueue.clear();
- imageDownloadMutex.unlock();
- }
-
- this_thread::sleep_for(chrono::milliseconds(20));
- }
- });
-
- checkContentAccessTimeThread = thread([this]
- {
- while(alive)
- {
- this_thread::sleep_for(chrono::milliseconds(500));
- lock_guard<mutex> lock(imageDownloadMutex);
-
- i64 now = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
- for(unordered_map<string, ContentByUrlResult>::iterator it = contentUrlCache.begin(); it != contentUrlCache.end();)
- {
- if(it->second.type == ContentByUrlResult::Type::CACHED && now - it->second.lastAccessed > CONTENT_NOT_VISIBLE_AGE_MS)
- {
- switch(it->second.cachedType)
- {
- case ContentByUrlResult::CachedType::TEXTURE:
- {
- if(ImagePreview::getPreviewContentPtr() == it->second.texture)
- {
- ++it;
- continue;
- }
- delete it->second.texture;
- break;
- }
- case ContentByUrlResult::CachedType::GIF:
- {
- if(ImagePreview::getPreviewContentPtr() == it->second.texture)
- {
- ++it;
- continue;
- }
- delete it->second.gif;
- break;
- }
- case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
- {
- delete it->second.webPagePreview;
- break;
- }
- default:
- ++it;
- continue;
- }
- it = contentUrlCache.erase(it);
- }
- else
- ++it;
- }
- }
- });
- }
-
- Cache::~Cache()
- {
- alive = false;
- downloadWaitThread.join();
- checkContentAccessTimeThread.join();
- }
-
- static void createFileIgnoreError(const boost::filesystem::path &path)
- {
- try
- {
- fileReplace(path, StringView("", 0));
- }
- catch(FileException &e)
- {
- fprintf(stderr, "Failed to create empty file: %s, reason: %s\n", path.string().c_str(), e.what());
- }
- }
-
- const ContentByUrlResult Cache::getContentByUrl(const string &url, int downloadLimitBytes)
- {
- lock_guard<mutex> lock(imageDownloadMutex);
- auto it = contentUrlCache.find(url);
- if(it != contentUrlCache.end())
- {
- it->second.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
- return it->second;
- }
-
- // TODO: Verify hashed url is not too long for filepath on windows
- boost::filesystem::path filepath = getImagesDir();
- odhtdb::Hash urlHash(url.data(), url.size());
- filepath /= urlHash.toString();
-
- boost::filesystem::path downloadingFilepath = filepath;
- downloadingFilepath += ".downloading";
- if(boost::filesystem::exists(downloadingFilepath))
- {
- // Intentionally ignore failure, program should not crash if we fail to remove these files...
- boost::system::error_code err;
- boost::filesystem::remove(filepath, err);
- boost::filesystem::remove(downloadingFilepath, err);
- }
-
- // TODO: Do not load content in this thread. Return LOADING status and load it in another thread, because with a lot of images, chat can freeze
- ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath, true);
- if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
- {
- contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
- contentUrlCache[url] = contentByUrlResult;
- printf("Loaded content from file cache: %s\n", url.c_str());
- return contentByUrlResult;
- }
- else if(contentByUrlResult.type == ContentByUrlResult::Type::FAILED_DOWNLOAD && boost::filesystem::exists(filepath))
- {
- contentUrlCache[url] = contentByUrlResult;
- return contentByUrlResult;
- }
-
- createFileIgnoreError(downloadingFilepath);
- ContentByUrlResult result((sf::Texture*)nullptr, ContentByUrlResult::Type::DOWNLOADING);
- contentUrlCache[url] = result;
-
- string downloadLimitBytesStr = to_string(downloadLimitBytes);
-
- string escapedUrl = stringReplaceChar(url, "'", "");
- escapedUrl = stringReplaceChar(escapedUrl, "\\", "");
- Process::string_type cmd = "curl -L --silent -o '";
- cmd += filepath.native();
- cmd += "' --max-filesize " + downloadLimitBytesStr + " --range 0-" + downloadLimitBytesStr + " --url '" + escapedUrl + "'";
- // TODO: Use this instead of curl on windows: certutil.exe -urlcache -split -f "https://url/to/file" path/and/name/to/save/as/file
- Process *process = new Process(cmd, "", nullptr, nullptr, false);
- ImageDownloadInfo imageDownloadInfo { process, url };
- imageDownloadProcessesQueue.emplace_back(imageDownloadInfo);
- return result;
- }
-}
diff --git a/src/Channel.cpp b/src/Channel.cpp
deleted file mode 100644
index 9164f9d..0000000
--- a/src/Channel.cpp
+++ /dev/null
@@ -1,405 +0,0 @@
-#include "../include/Channel.hpp"
-#include <odhtdb/Database.hpp>
-#include <odhtdb/bin2hex.hpp>
-#include <cstring>
-#include <sibs/SafeSerializer.hpp>
-
-using namespace std;
-
-namespace dchat
-{
- static Channel *currentChannel;
-
- Channel::Channel(const string &_name, const odhtdb::DatabaseNode &_databaseNodeInfo, User *_localUser, std::shared_ptr<odhtdb::Database> _database) :
- database(_database),
- databaseNodeInfo(_databaseNodeInfo),
- name(_name),
- messageBoard(this),
- localUser(_localUser ? _localUser : new OfflineUser("You"))
- {
- bridgeServices.push_back(new DiscordService());
- addUserLocally(localUser);
-
- //addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif) deaf [emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash());
- //addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif)[emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash());
- //addLocalMessage(u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo", &systemUser, 0, odhtdb::Hash());
- //addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash());
-
- auto binds = Chatbar::getBinds();
- vector<string> suggestionsStr;
- suggestionsStr.reserve(binds.size());
- for(auto &bind : binds)
- {
- string suggestion = bind.first;
- suggestion += " ";
- suggestion += bind.second;
- suggestionsStr.emplace_back(move(suggestion));
- }
- suggestions.show(suggestionsStr);
-
- if(database)
- {
- database->seed(databaseNodeInfo, odhtdb::DatabaseFetchOrder::NEWEST_FIRST);
-
- pingKey = odhtdb::DhtKey(*databaseNodeInfo.getRequestHash()).getPingKey();
- // TODO: Ban peers that spam this key (take in account that creator of packets can be forged)
- pingListener = database->receiveCustomMessage(pingKey, [this](const void *data, usize size)
- {
- sibs::SafeSerializer result;
- try
- {
- sibs::SafeDeserializer deserializer((const u8*)data, size);
- u8 userPublicKeyRaw[odhtdb::PUBLIC_KEY_NUM_BYTES];
- deserializer.extract(userPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES);
- odhtdb::Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES);
- auto user = getUserByPublicKey(userPublicKey);
- if(!user)
- {
- // TODO: Ban peer if this happens too often
- return result;
- }
-
- string unsignedData = userPublicKey.unsign(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize()));
- sibs::SafeDeserializer unsignedDeserializer((const u8*)unsignedData.data(), unsignedData.size());
- u32 pingTimestampSec = unsignedDeserializer.extract<u32>();
- if(pingTimestampSec > user->pingTimestampSec)
- {
- user->pingTimestampSec = pingTimestampSec;
- }
- }
- catch(std::exception &e)
- {
- fprintf(stderr, "Failed while deseralizing ping\n");
- }
- return result;
- });
-
- sendPing(database->getSyncedTimestampUtc().seconds);
- }
- }
-
- Channel::~Channel()
- {
- for(BridgeService *bridgeService : bridgeServices)
- {
- delete bridgeService;
- }
-
- if(database)
- {
- database->cancelNodeListener(pingListener);
- database->stopSeeding(*databaseNodeInfo.getRequestHash());
- sendPing(0);
- }
-
- for(User *user : users)
- {
- delete user;
- }
-
- for(auto &discordUserIt : discordUserById)
- {
- delete discordUserIt.second;
- }
- }
-
- User* Channel::getLocalUser()
- {
- return localUser;
- }
-
- SystemUser* Channel::getSystemUser()
- {
- return &systemUser;
- }
-
- MessageBoard& Channel::getMessageBoard()
- {
- return messageBoard;
- }
-
- const string& Channel::getName() const
- {
- return name;
- }
-
- const vector<User*>& Channel::getUsers() const
- {
- return users;
- }
-
- OnlineUser* Channel::getUserByPublicKey(const odhtdb::Signature::PublicKey &publicKey)
- {
- auto userIt = publicKeyOnlineUsersMap.find(publicKey);
- if(userIt != publicKeyOnlineUsersMap.end())
- return userIt->second;
- return nullptr;
- }
-
- const odhtdb::DatabaseNode& Channel::getNodeInfo() const
- {
- return databaseNodeInfo;
- }
-
- Message* Channel::getLatestMessage()
- {
- return messageBoard.getLatestMessage();
- }
-
- void Channel::addLocalMessage(const string &msg, User *owner, u64 timestampSeconds)
- {
- addLocalMessage(msg, owner, timestampSeconds, odhtdb::Hash());
- }
-
- void Channel::addLocalMessage(const string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id)
- {
- assert(owner);
- if(timestampSeconds == 0)
- {
- timestampSeconds = time(NULL);
- }
- messageBoard.addMessage(new Message(owner, msg, timestampSeconds), id);
- }
-
- void Channel::addLocalDiscordMessage(const string &discordUserName, u64 discordUserId, const string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id)
- {
- assert(owner);
- if(timestampSeconds == 0)
- {
- timestampSeconds = time(NULL);
- }
-
- OnlineDiscordUser *discordUser = nullptr;
- auto discordUserIt = discordUserById.find(discordUserId);
- if(discordUserIt == discordUserById.end())
- {
- discordUser = new OnlineDiscordUser(discordUserName, discordUserId, owner);
- discordUserById[discordUserId] = discordUser;
- }
- else
- {
- // TODO: What if several users bridge same chat? the same discord user id could belong to different owners.
- // Dchat channels should only allow one user to bridge data from one discord channel. Bridging data between multiple discord channels to
- // one dchat channel should be allowed.
- discordUser = discordUserIt->second;
- }
- messageBoard.addMessage(new Message(discordUser, msg, timestampSeconds), id);
- }
-
- void Channel::addSystemMessage(const string &msg, bool plainText)
- {
- u64 timestampSeconds = time(NULL);
- messageBoard.addMessage(new Message(&systemUser, msg, timestampSeconds, plainText), odhtdb::Hash());
- }
-
- void Channel::addMessage(const string &msg)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
-
- sibs::SafeSerializer serializer;
- serializer.add(ChannelDataType::ADD_MESSAGE);
- serializer.add((const u8*)msg.data(), msg.size());
-
- database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
- else
- addLocalMessage(msg, localUser, 0, odhtdb::Hash());
- }
-
- void Channel::deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
- {
- messageBoard.deleteMessage(id, requestedByUser);
- }
-
- void Channel::deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
-
- sibs::SafeSerializer serializer;
- serializer.add(ChannelDataType::DELETE_MESSAGE);
- serializer.add((const u8*)id.getData(), odhtdb::HASH_BYTE_SIZE);
-
- database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
- else
- deleteLocalMessage(id, requestedByUser);
- }
-
- void Channel::addUserLocally(User *user)
- {
- users.push_back(user);
- user->avatarUrl = "https://discordemoji.com/assets/emoji/HanekawaSmug.gif";
- if(user->isOnlineUser())
- {
- auto onlineUser = static_cast<OnlineUser*>(user);
- publicKeyOnlineUsersMap[onlineUser->getPublicKey()] = onlineUser;
- }
- }
-
- bool Channel::addUser(const odhtdb::Signature::PublicKey &userId, const odhtdb::DataView &groupId)
- {
- assert(database);
- if(!database || localUser->type != User::Type::ONLINE_LOCAL_USER)
- 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 onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
- try
- {
- database->addUser(databaseNodeInfo, onlineLocalUser->keyPair, userId, groupId);
- return true;
- }
- catch(std::exception &e)
- {
- fprintf(stderr, "Group with id %s does not exist in channel %s or you do not have permission to add user to that group\nError: %s\n", odhtdb::bin2hex((const char*)groupId.data, groupId.size).c_str(), databaseNodeInfo.getRequestHash()->toString().c_str(), e.what());
- return false;
- }
- }
-
- void Channel::replaceLocalUser(OnlineLocalUser *newOnlineLocalUser)
- {
- for(vector<User*>::iterator it = users.begin(); it != users.end(); ++it)
- {
- if(*it == localUser)
- {
- users.erase(it);
- delete localUser;
- break;
- }
- }
-
- localUser = newOnlineLocalUser;
- addUserLocally(newOnlineLocalUser);
- }
-
- void Channel::changeNick(const string &newNick)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
-
- sibs::SafeSerializer serializer;
- serializer.add(ChannelDataType::NICKNAME_CHANGE);
- serializer.add((u8)newNick.size());
- serializer.add((const u8*)newNick.data(), newNick.size());
-
- database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
- }
-
- void Channel::setAvatar(const string &newAvatarUrl)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
-
- sibs::SafeSerializer serializer;
- serializer.add(ChannelDataType::CHANGE_AVATAR);
- serializer.add((u16)newAvatarUrl.size());
- serializer.add((const u8*)newAvatarUrl.data(), newAvatarUrl.size());
-
- database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
- }
-
- void Channel::setNameLocally(const string &name)
- {
- this->name = name;
- }
-
- void Channel::setName(const string &name)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
-
- sibs::SafeSerializer serializer;
- serializer.add(ChannelDataType::CHANGE_CHANNEL_NAME);
- serializer.add((u16)name.size());
- serializer.add((const u8*)name.data(), name.size());
-
- database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- }
- }
-
- int Channel::getUserLowestPermissionLevel(OnlineUser *user) const
- {
- if(!database) return -1;
- return database->getUserLowestPermissionLevel(*databaseNodeInfo.getRequestHash(), user->getPublicKey());
- }
-
- void Channel::processEvent(const sf::Event &event, Cache &cache)
- {
- chatbar.processEvent(event, cache, this);
- messageBoard.processEvent(event, cache);
- }
-
- void Channel::draw(sf::RenderWindow &window, Cache &cache)
- {
- messageBoard.draw(window, cache);
- chatbar.draw(window, cache);
- //suggestions.draw(window, cache);
- }
-
- void Channel::update()
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER && pingTimer.getElapsedTime().asMilliseconds() > 5000)
- {
- pingTimer.restart();
- sendPing(database->getSyncedTimestampUtc().seconds);
- }
- }
-
- void Channel::sendPing(u32 pingTimestampSec)
- {
- if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
- {
- //printf("Sending ping, counter: %u\n", pingCounter);
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
- sibs::SafeSerializer serializer;
- serializer.add((const u8*)onlineLocalUser->getPublicKey().getData(), onlineLocalUser->getPublicKey().getSize());
-
- sibs::SafeSerializer signedSerializer;
- signedSerializer.add(pingTimestampSec);
- string signedData = onlineLocalUser->keyPair.getPrivateKey().sign(odhtdb::DataView(signedSerializer.getBuffer().data(), signedSerializer.getBuffer().size()));
- serializer.add((const u8*)signedData.data(), signedData.size());
- database->sendCustomMessage(pingKey, serializer.getBuffer().data(), serializer.getBuffer().size());
- }
- }
-
- u32 Channel::getSyncedTimestampUtcInSec()
- {
- if(!database)
- return 0;
- return database->getSyncedTimestampUtc().seconds;
- }
-
- const vector<BridgeService*>& Channel::getBridgeServices() const
- {
- return bridgeServices;
- }
-
- DiscordService* Channel::getDiscordService()
- {
- return (DiscordService*)bridgeServices[0];
- }
-
- void Channel::setCurrent(Channel *channel)
- {
- currentChannel = channel;
- }
-
- Channel* Channel::getCurrent()
- {
- return currentChannel;
- }
-}
diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp
index 6a459cc..4273f2c 100644
--- a/src/Chatbar.cpp
+++ b/src/Chatbar.cpp
@@ -1,12 +1,13 @@
#include "../include/Chatbar.hpp"
+#include "../include/MessageBoard.hpp"
#include "../include/ResourceCache.hpp"
#include "../include/Settings.hpp"
-#include "../include/Channel.hpp"
-#include "../include/ChannelSidePanel.hpp"
+#include "../include/RoomSidePanel.hpp"
#include "../include/UsersSidePanel.hpp"
#include "../include/Command.hpp"
#include "../include/ColorScheme.hpp"
-#include "../include/Cache.hpp"
+#include <dchat/Storage.hpp>
+#include <dchat/Room.hpp>
#include <cmath>
#include <cstring>
#include <process.hpp>
@@ -26,7 +27,7 @@ namespace dchat
const float LINE_PADDING_SIDE = 20.0f;
const float LINE_HEIGHT = 1.0f;
- unordered_map<string, string> binds;
+ static unordered_map<string, string> binds;
Chatbar::Chatbar() :
text("", ResourceCache::getFont("fonts/Nunito-Regular.ttf"), FONT_SIZE * Settings::getScaling(), 0),
@@ -49,7 +50,7 @@ namespace dchat
ArgParseException(const string &errMsg) : std::runtime_error(errMsg) {}
};
- string stringRemoveQuotes(const StringView &str)
+ static string stringRemoveQuotes(const StringView &str)
{
string result;
result.reserve(str.size);
@@ -62,7 +63,7 @@ namespace dchat
return result;
}
- vector<string> splitCommandArgs(const StringView &str)
+ static vector<string> splitCommandArgs(const StringView &str)
{
vector<string> result;
ssize offset = 0;
@@ -155,7 +156,7 @@ namespace dchat
}
}
- void Chatbar::processEvent(const sf::Event &event, Cache &cache, Channel *channel)
+ void Chatbar::processEvent(const sf::Event &event, Cache *cache, std::shared_ptr<Room> room, MessageBoard *messageBoard)
{
if(!focused) return;
@@ -181,7 +182,10 @@ namespace dchat
else
{
replaceBinds(msg);
- channel->addMessage(msg);
+ if(room)
+ room->publishMessage(msg);
+ else
+ messageBoard->addOfflineUserMessage(std::move(msg), false);
}
text.setString("");
}
@@ -190,25 +194,25 @@ namespace dchat
}
}
- void Chatbar::draw(sf::RenderWindow &window, Cache &cache)
+ void Chatbar::draw(sf::RenderWindow &window, Cache *cache)
{
auto windowSize = window.getSize();
text.setCharacterSize(FONT_SIZE * Settings::getScaling());
const float fontHeight = text.getFont()->getLineSpacing(text.getCharacterSize());
- sf::RectangleShape lineShape(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - LINE_PADDING_SIDE * Settings::getScaling() * 2.0f), LINE_HEIGHT));
+ sf::RectangleShape lineShape(sf::Vector2f(floor(windowSize.x - RoomSidePanel::getWidth() - LINE_PADDING_SIDE * Settings::getScaling() * 2.0f), LINE_HEIGHT));
lineShape.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(10, 10, 10));
- lineShape.setPosition(ChannelSidePanel::getWidth() + LINE_PADDING_SIDE * Settings::getScaling(), floor(windowSize.y - getHeight()));
+ lineShape.setPosition(RoomSidePanel::getWidth() + LINE_PADDING_SIDE * Settings::getScaling(), floor(windowSize.y - getHeight()));
window.draw(lineShape);
- sf::Vector2f inputBackgroundSize(floor(windowSize.x - ChannelSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f));
- sf::Vector2f backgroundSize(floor(windowSize.x - ChannelSidePanel::getWidth()), floor(getHeight() - LINE_HEIGHT));
+ sf::Vector2f inputBackgroundSize(floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f));
+ sf::Vector2f backgroundSize(floor(windowSize.x - RoomSidePanel::getWidth()), floor(getHeight() - LINE_HEIGHT));
background.setSize(backgroundSize);
- background.setPosition(ChannelSidePanel::getWidth(), floor(windowSize.y - backgroundSize.y));
+ background.setPosition(RoomSidePanel::getWidth(), floor(windowSize.y - backgroundSize.y));
window.draw(background);
- sf::Vector2f inputBackgroundPos(floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - inputBackgroundSize.y - PADDING_BOTTOM * Settings::getScaling()));
+ sf::Vector2f inputBackgroundPos(floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - inputBackgroundSize.y - PADDING_BOTTOM * Settings::getScaling()));
inputBackground.setSize(inputBackgroundSize);
inputBackground.setPosition(inputBackgroundPos);
text.setPosition(floor(inputBackgroundPos.x + BOX_PADDING_X), floor(inputBackgroundPos.y + inputBackgroundSize.y * 0.5f - fontHeight * 0.5f));
@@ -221,7 +225,7 @@ namespace dchat
sf::Vector2f Chatbar::getInputPosition(sf::RenderWindow &window)
{
auto windowSize = window.getSize();
- return { floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - getInputSize(window).y - PADDING_BOTTOM * Settings::getScaling()) };
+ return { floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - getInputSize(window).y - PADDING_BOTTOM * Settings::getScaling()) };
}
sf::Vector2f Chatbar::getInputSize(sf::RenderWindow &window)
@@ -229,7 +233,7 @@ namespace dchat
auto windowSize = window.getSize();
const float fontSize = FONT_SIZE * Settings::getScaling();
const float fontHeight = ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize);
- return { floor(windowSize.x - ChannelSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) };
+ return { floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) };
}
float Chatbar::getHeight()
@@ -246,7 +250,7 @@ namespace dchat
binds[key] = value;
if(updateFile)
- Cache::replaceBindsInFile(binds);
+ replaceBindsInFile(binds);
return true;
}
@@ -258,9 +262,17 @@ namespace dchat
binds.erase(it);
if(updateFile)
- Cache::replaceBindsInFile(binds);
+ replaceBindsInFile(binds);
return true;
}
+
+ void Chatbar::loadBindsFromFile()
+ {
+ dchat::loadBindsFromFile([](const std::string &key, const std::string &value)
+ {
+ binds[key] = value;
+ });
+ }
const unordered_map<string, string>& Chatbar::getBinds()
{
diff --git a/src/Gif.cpp b/src/Gif.cpp
index 6c18851..0015f50 100644
--- a/src/Gif.cpp
+++ b/src/Gif.cpp
@@ -1,216 +1,29 @@
#include "../include/Gif.hpp"
-#include "../include/FileUtil.hpp"
-
-using namespace std;
namespace dchat
{
- void* bitmapCreate(int width, int height)
- {
- return calloc(width * height, 4);
- }
-
- void bitmapDestroy(void *bitmap)
- {
- free(bitmap);
- }
-
- unsigned char* bitmapGetBuffer(void *bitmap)
- {
- return (unsigned char*)bitmap;
- }
-
- void bitmapSetOpaque(void *bitmap, bool opaque)
- {
-
- }
-
- bool bitmapTestOpaque(void *bitmap)
- {
- return false;
- }
-
- void bitmapModified(void *bitmap)
- {
-
- }
-
- const char* gifResultToString(gif_result code)
- {
- switch(code)
- {
- case GIF_INSUFFICIENT_FRAME_DATA:
- return "GIF_INSUFFICIENT_FRAME_DATA";
- case GIF_FRAME_DATA_ERROR:
- return "GIF_FRAME_DATA_ERROR";
- case GIF_INSUFFICIENT_DATA:
- return "GIF_INSUFFICIENT_DATA";
- case GIF_DATA_ERROR:
- return "GIF_DATA_ERROR";
- case GIF_INSUFFICIENT_MEMORY:
- return "GIF_INSUFFICIENT_MEMORY";
- default:
- return "Unknown gif result code";
- }
- }
-
- Gif::Gif(const boost::filesystem::path &filepath) :
- currentFrame(0),
- timeElapsedCs(0.0)
- {
- try
- {
- fileContent = getFileContent(filepath);
- }
- catch(FileException &e)
- {
- throw GifLoadException(e.what());
- }
-
- try
- {
- init();
- }
- catch(GifLoadException &e)
- {
- delete[] fileContent.data;
- throw e;
- }
- }
-
- Gif::Gif(StringView &&_fileContent) :
- fileContent(move(_fileContent)),
- currentFrame(0),
- timeElapsedCs(0.0)
+ SfmlGif::SfmlGif(StringView fileContent) : Gif(fileContent)
{
- try
- {
- init();
- }
- catch(GifLoadException &e)
- {
- delete[] fileContent.data;
- throw e;
- }
+
}
-
- void Gif::init()
+
+
+ bool SfmlGif::createTexture()
{
- gif_bitmap_callback_vt bitmapCallbacks =
- {
- bitmapCreate,
- bitmapDestroy,
- bitmapGetBuffer,
- bitmapSetOpaque,
- bitmapTestOpaque,
- bitmapModified
- };
-
- gif_create(&gif, &bitmapCallbacks);
-
- gif_result code;
- do
+ Vec2u size = getSize();
+ if(!texture.create(size.x, size.y))
{
- code = gif_initialise(&gif, fileContent.size, (unsigned char*)fileContent.data);
- if(code != GIF_OK && code != GIF_WORKING)
- {
- string errMsg = "Failed to initialize gif, reason: ";
- errMsg += gifResultToString(code);
- throw GifLoadException(errMsg);
- }
+ fprintf(stderr, "Failed to create texture for gif!\n");
+ return false;
}
- while(code != GIF_OK);
-
- if(!texture.create(gif.width, gif.height))
- throw GifLoadException("Failed to create texture for gif");
-
+
texture.setSmooth(true);
- sprite.setTexture(texture, true);
- }
-
- Gif::~Gif()
- {
- gif_finalise(&gif);
- delete[] fileContent.data;
- }
-
- sf::Vector2u Gif::getSize() const
- {
- return sprite.getTexture()->getSize();
- }
-
- void Gif::setPosition(const sf::Vector2f &position)
- {
- sprite.setPosition(position);
+ texture.generateMipmap();
+ return true;
}
-
- sf::Vector2f Gif::getPosition() const
- {
- return sprite.getPosition();
- }
-
- void Gif::setScale(const sf::Vector2f &scale)
- {
- sprite.setScale(scale);
- }
-
- void Gif::setColor(sf::Color color)
- {
- sprite.setColor(color);
- }
-
- void Gif::draw(sf::RenderTarget &target, const sf::RenderStates &renderState)
- {
- double timeElapsedMilli = (double)frameTimer.getElapsedTime().asMilliseconds();
- // If gif is not redrawn for a while, then we reset timer (gif is paused). This happens when gif is not visible and then appears visible
- // (because it's visible in window). The reason this is done is to prevent too much time between rendering gif frames, as processing a gif
- // requires to process all frames between two points in time, if elapsed frame time is too high, then we would require to process several
- // frames of gif in one application render frame.
- if(timeElapsedMilli > 1000.0)
- timeElapsedMilli = 0.0;
- double frameDeltaCs = timeElapsedMilli * 0.1; // Centisecond
- frameTimer.restart();
- timeElapsedCs += frameDeltaCs;
-
- unsigned char *image = nullptr;
- u32 startFrame = currentFrame;
- while(true)
- {
- u32 i = currentFrame % gif.frame_count;
- gif_result code = gif_decode_frame(&gif, i);
- if(code != GIF_OK)
- {
- printf("Warning: gif_decode_frame: %s\n", gifResultToString(code));
- break;
- }
-
- gif_frame &frame = gif.frames[i];
- // frame_delay is in centiseconds
- unsigned int frameDelay = frame.frame_delay;
- if(frameDelay == 0)
- frameDelay = 7;
- double fFrameDelay = (double)frameDelay;
- if(timeElapsedCs >= fFrameDelay)
- timeElapsedCs -= fFrameDelay;
- else
- break;
-
- image = (unsigned char*)gif.frame_image;
- ++currentFrame;
- }
-
- if(currentFrame != startFrame)
- {
- texture.update(image);
- // TODO: Check if this is too heavy
- texture.generateMipmap();
- sprite.setTexture(texture, true);
- }
- target.draw(sprite, renderState);
- }
-
- bool Gif::isDataGif(const StringView &data)
+
+ void SfmlGif::updateTexture(void *textureData)
{
- return data.size >= 6 && (memcmp(data.data, "GIF87a", 6) == 0 || memcmp(data.data, "GIF89a", 6) == 0);
+ texture.update((const sf::Uint8*)textureData);
}
-}
+} \ No newline at end of file
diff --git a/src/ImagePreview.cpp b/src/ImagePreview.cpp
index 0b36dbd..683bc1f 100644
--- a/src/ImagePreview.cpp
+++ b/src/ImagePreview.cpp
@@ -1,8 +1,8 @@
#include "../include/ImagePreview.hpp"
#include "../include/Settings.hpp"
-#include "../include/StringUtils.hpp"
#include "../include/Gif.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
+#include <dchat/Process.hpp>
#include <cassert>
namespace dchat
@@ -22,23 +22,25 @@ namespace dchat
void ImagePreview::preview(sf::Texture *texture, const std::string &url)
{
- if(texture == getInstance()->texture) return;
- getInstance()->texture = texture;
- getInstance()->contentType = texture ? ContentType::TEXTURE : ContentType::NONE;
+ ImagePreview *instance = getInstance();
+ if(texture == instance->texture) return;
+ instance->texture = texture;
+ instance->contentType = texture ? ContentType::TEXTURE : ContentType::NONE;
imagePreviewUrl = url;
if(texture)
- getInstance()->sprite.setTexture(*texture, true);
+ instance->sprite.setTexture(*texture, true);
else
- getInstance()->sprite = sf::Sprite();
+ instance->sprite = sf::Sprite();
}
void ImagePreview::preview(Gif *gif, const std::string &url)
{
- if(gif == getInstance()->gif) return;
- getInstance()->gif = gif;
- getInstance()->contentType = gif ? ContentType::GIF : ContentType::NONE;
+ ImagePreview *instance = getInstance();
+ if(gif == instance->gif) return;
+ instance->gif = gif;
+ instance->contentType = gif ? ContentType::GIF : ContentType::NONE;
imagePreviewUrl = url;
- getInstance()->sprite = sf::Sprite();
+ instance->sprite = sf::Sprite();
}
void* ImagePreview::getPreviewContentPtr()
@@ -53,22 +55,14 @@ namespace dchat
void ImagePreview::processEvent(const sf::Event &event)
{
- if(getInstance()->contentType == ContentType::NONE) return;
+ ImagePreview *instance = getInstance();
+ if(instance->contentType == ContentType::NONE) return;
if(event.mouseButton.button == sf::Mouse::Button::Left)
{
- sf::Vector2f imagePos;
- switch(getInstance()->contentType)
- {
- case ContentType::TEXTURE:
- imagePos = getInstance()->sprite.getPosition();
- break;
- case ContentType::GIF:
- imagePos = getInstance()->gif->getPosition();
- break;
- }
- getInstance()->sprite.getPosition();
- const auto &imageSize = getInstance()->size;
+ const auto &imagePos = instance->position;
+ const auto &imageSize = instance->size;
+
bool mouseInside = false;
if(event.mouseButton.x >= imagePos.x && event.mouseButton.x <= imagePos.x + imageSize.x &&
event.mouseButton.y >= imagePos.y && event.mouseButton.y <= imagePos.y + imageSize.y)
@@ -79,10 +73,8 @@ namespace dchat
if(event.type == sf::Event::MouseButtonPressed && mouseInside && !imagePreviewUrl.empty())
{
// TODO: Implement for other platforms than linux
- std::string escapedUrl = stringReplaceChar(imagePreviewUrl, "'", "");
- escapedUrl = stringReplaceChar(escapedUrl, "\\", "");
std::string cmd = "xdg-open '";
- cmd += escapedUrl;
+ cmd += escapeCommand(imagePreviewUrl);
cmd += "'";
printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str());
system(cmd.c_str());
@@ -100,36 +92,43 @@ namespace dchat
void ImagePreview::draw(sf::RenderWindow &window)
{
- if(getInstance()->contentType == ContentType::NONE) return;
+ ImagePreview *instance = getInstance();
+ if(instance->contentType == ContentType::NONE) return;
auto windowSize = window.getSize();
sf::RectangleShape background(sf::Vector2f(windowSize.x, windowSize.y));
background.setFillColor(sf::Color(0, 0, 0, 200));
- auto imageSize = getInstance()->calculateImageSize(windowSize);
- getInstance()->size = imageSize;
+ auto imageSize = instance->calculateImageSize(windowSize);
+ instance->size = imageSize;
window.draw(background);
- switch(getInstance()->contentType)
+ switch(instance->contentType)
{
case ContentType::TEXTURE:
{
- auto textureSize = getInstance()->sprite.getTexture()->getSize();
- getInstance()->sprite.setPosition(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2);
- getInstance()->sprite.setScale((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y);
- window.draw(getInstance()->sprite);
+ auto textureSize = instance->sprite.getTexture()->getSize();
+ instance->position = sf::Vector2f(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2);
+ instance->sprite.setPosition(instance->position);
+ instance->sprite.setScale((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y);
+ window.draw(instance->sprite);
break;
}
case ContentType::GIF:
{
- auto textureSize = getInstance()->gif->getSize();
- getInstance()->gif->setPosition(sf::Vector2f(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2));
- getInstance()->gif->setScale(sf::Vector2f((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y));
- getInstance()->gif->draw(window);
+ auto *gif = static_cast<SfmlGif*>(instance->gif);
+ gif->update();
+ auto textureSize = gif->getSize();
+
+ instance->position = sf::Vector2f(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2);
+ sf::Sprite sprite(gif->texture);
+ sprite.setPosition(instance->position);
+ sprite.setScale(sf::Vector2f((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y));
+ window.draw(sprite);
break;
}
}
- getInstance()->lastSeenTimer.restart();
+ instance->lastSeenTimer.restart();
}
sf::Vector2u ImagePreview::calculateImageSize(sf::Vector2u windowSize) const
@@ -143,7 +142,9 @@ namespace dchat
textureSize = texture->getSize();
break;
case ContentType::GIF:
- textureSize = gif->getSize();
+ auto size = gif->getSize();
+ textureSize.x = size.x;
+ textureSize.y = size.y;
break;
}
auto imageSize = textureSize;
diff --git a/src/Message.cpp b/src/Message.cpp
index ca50821..1408423 100644
--- a/src/Message.cpp
+++ b/src/Message.cpp
@@ -1,18 +1,15 @@
#include "../include/Message.hpp"
#include "../include/ResourceCache.hpp"
#include "../include/Settings.hpp"
-#include "../include/ColorScheme.hpp"
-
-using namespace std;
+#include <dchat/Room.hpp>
namespace dchat
{
- Message::Message(User *_user, const std::string &_text, u64 _timestampSeconds, bool plainText) :
- user(_user),
- text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 18 * Settings::getScaling(), 0.0f, plainText),
- timestampSeconds(_timestampSeconds),
- type(Type::REGULAR)
+ Message::Message(std::shared_ptr<RoomMessage> _roomMessage, bool plainText) :
+ roomMessage(_roomMessage),
+ text(_roomMessage->text, ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 18.0f * Settings::getScaling(), 0, plainText),
+ onlineUser(true)
{
- text.setFillColor(ColorScheme::getTextRegularColor());
+
}
-}
+} \ No newline at end of file
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index 4366d9b..d058f82 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -1,21 +1,24 @@
#include "../include/MessageBoard.hpp"
#include "../include/Settings.hpp"
#include "../include/ResourceCache.hpp"
-#include "../include/Gif.hpp"
-#include "../include/ChannelSidePanel.hpp"
+#include "../include/RoomSidePanel.hpp"
#include "../include/UsersSidePanel.hpp"
-#include "../include/ChannelTopPanel.hpp"
+#include "../include/RoomTopPanel.hpp"
#include "../include/Chatbar.hpp"
#include "../include/ColorScheme.hpp"
+#include "../include/StaticImage.hpp"
+#include "../include/Gif.hpp"
#include "../include/Theme.hpp"
#include "../include/GlobalContextMenu.hpp"
-#include "../include/Channel.hpp"
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Window/Mouse.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Text.hpp>
+#include <dchat/Cache.hpp>
+#include <dchat/Room.hpp>
+#include <dchat/User.hpp>
#include <cmath>
using namespace std;
@@ -42,8 +45,8 @@ namespace dchat
// Merge messages from same user that are sent within one minute
const int MERGE_TEXT_TIMESTAMP_DIFF_SEC = 60;
- MessageBoard::MessageBoard(Channel *_channel) :
- channel(_channel),
+ MessageBoard::MessageBoard(std::shared_ptr<Room> _room) :
+ room(_room),
scroll(0.0),
scrollSpeed(0.0),
totalHeight(0.0),
@@ -53,10 +56,16 @@ namespace dchat
{
scrollbar.backgroundColor = sf::Color(49, 52, 57);
scrollbar.scrollColor = sf::Color(37, 39, 44);
+
+ offlineUser = make_shared<User>(odhtdb::Signature::PublicKey());
+ offlineUser->nickname = "You";
+ systemUser = make_shared<User>(odhtdb::Signature::PublicKey());
+ systemUser->nickname = "System";
}
MessageBoard::~MessageBoard()
{
+ // TODO: Re-add
for(Message *message : messages)
{
delete message;
@@ -68,7 +77,7 @@ namespace dchat
{
for(usize i = 0; i < messages.size(); ++i)
{
- if(message->timestampSeconds < messages[i]->timestampSeconds)
+ if(message->roomMessage->timestampSeconds < messages[i]->roomMessage->timestampSeconds)
return i;
}
return messages.size();
@@ -80,38 +89,41 @@ namespace dchat
// throw std::runtime_error("Failed to create render target for message board!");
dirty = true;
}
-
- bool MessageBoard::addMessage(Message *message, const odhtdb::Hash &id)
+
+ bool MessageBoard::addMessage(Message *message)
{
lock_guard<mutex> lock(messageProcessMutex);
- bool emptyHash = id.isEmpty();
- if(!emptyHash && messageIdMap.find(id) != messageIdMap.end())
+ bool emptyHash = message->roomMessage->id.isEmpty();
+ if(!emptyHash && messageIdMap.find(message->roomMessage->id) != messageIdMap.end())
{
delete message;
return false;
}
+
auto positionToAddMessage = findPositionToInsertMessageByTimestamp(message);
if(positionToAddMessage == messages.size())
scrollToBottom = true;
+
messages.insert(messages.begin() + positionToAddMessage, message);
- message->id = id;
+
if(!emptyHash)
- messageIdMap[id] = message;
+ messageIdMap[message->roomMessage->id] = message;
+
dirty = true;
return true;
}
-
+
void MessageBoard::deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
{
lock_guard<mutex> lock(messageProcessMutex);
auto it = messageIdMap.find(id);
if(it == messageIdMap.end()) return;
- if(it->second->user->isOnlineUser())
+ if(it->second->onlineUser)
{
- auto onlineUser = static_cast<const OnlineUser*>(it->second->user);
- if(onlineUser->getPublicKey() != requestedByUser)
+ const auto &publicKey = it->second->roomMessage->creator->publicKey;
+ if(publicKey != requestedByUser)
{
- fprintf(stderr, "Warning: user %s requested to delete a message owned by user %s, ignoring request\n", requestedByUser.toString().c_str(), onlineUser->getPublicKey().toString().c_str());
+ fprintf(stderr, "Warning: user %s requested to delete a message owned by user %s, ignoring request\n", requestedByUser.toString().c_str(), publicKey.toString().c_str());
return;
}
}
@@ -128,9 +140,33 @@ namespace dchat
break;
}
}
+
+ dirty = true;
+ }
+
+ void MessageBoard::addOfflineUserMessage(std::string msg, bool plainText)
+ {
+ auto roomMessage = make_shared<RoomMessage>();
+ roomMessage->creator = offlineUser;
+ roomMessage->timestampSeconds = time(NULL);
+ roomMessage->text = move(msg);
+ Message *message = new Message(roomMessage, plainText);
+ message->onlineUser = false;
+ addMessage(message);
+ }
+
+ void MessageBoard::addSystemUserMessage(std::string msg, bool plainText)
+ {
+ auto roomMessage = make_shared<RoomMessage>();
+ roomMessage->creator = systemUser;
+ roomMessage->timestampSeconds = time(NULL);
+ roomMessage->text = move(msg);
+ Message *message = new Message(roomMessage, plainText);
+ message->onlineUser = false;
+ addMessage(message);
}
- void MessageBoard::drawDefault(sf::RenderWindow &window, Cache &cache)
+ void MessageBoard::drawDefault(sf::RenderWindow &window, Cache *cache)
{
const float LINE_SPACING = 5.0f * Settings::getScaling();
const float MESSAGE_PADDING_TOP = 25.0f;
@@ -149,7 +185,7 @@ namespace dchat
sf::Shader *circleShader = ResourceCache::getShader("shaders/circleMask.glsl", sf::Shader::Fragment);
- sf::Vector2<double> position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling(), ChannelTopPanel::getHeight() + PADDING_TOP);
+ sf::Vector2<double> position(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling(), RoomTopPanel::getHeight() + PADDING_TOP);
double startHeight = position.y;
position.y += scroll;
@@ -163,15 +199,19 @@ namespace dchat
bool mergeTextWithPrev = false;
if(i > 0)
{
- Message *prevMessage = messages[i - 1];
- mergeTextWithPrev = prevMessage->user == message->user && (message->timestampSeconds == 0 || message->timestampSeconds - prevMessage->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC);
+ const Message *prevMessage = messages[i - 1];
+ mergeTextWithPrev = prevMessage->roomMessage->creator == message->roomMessage->creator &&
+ (message->roomMessage->timestampSeconds == 0 ||
+ message->roomMessage->timestampSeconds - prevMessage->roomMessage->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC);
}
bool mergeTextWithNext = false;
if(i < numMessages - 1)
{
- Message *nextMessage = messages[i + 1];
- mergeTextWithNext = nextMessage->user == message->user && (nextMessage->timestampSeconds == 0 || nextMessage->timestampSeconds - message->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC);
+ const Message *nextMessage = messages[i + 1];
+ mergeTextWithNext = nextMessage->roomMessage->creator == message->roomMessage->creator &&
+ (nextMessage->roomMessage->timestampSeconds == 0 ||
+ nextMessage->roomMessage->timestampSeconds - message->roomMessage->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC);
}
bool visible = false;
@@ -185,23 +225,24 @@ namespace dchat
{
visible = true;
string usernameStr;
- if(message->user->type == User::Type::ONLINE_DISCORD_USER)
- {
- usernameStr = "(Discord) ";
- usernameStr += message->user->getName();
- }
- else
- {
- usernameStr = message->user->getName();
- }
+ // TODO: Add bridge user
+ // if(message->creator->type == User::Type::ONLINE_DISCORD_USER)
+ // {
+ // usernameStr = "(Discord) ";
+ // usernameStr += message->user->getName();
+ // }
+ // else
+ // {
+ usernameStr = message->roomMessage->creator->nickname;
+ //}
sf::Text usernameText(sf::String::fromUtf8(usernameStr.begin(), usernameStr.end()), *usernameFont, usernameTextCharacterSize);
usernameText.setFillColor(sf::Color(15, 192, 252));
usernameText.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
window.draw(usernameText);
- if(message->timestampSeconds)
+ if(message->roomMessage->timestampSeconds)
{
- time_t time = (time_t)message->timestampSeconds;
+ time_t time = (time_t)message->roomMessage->timestampSeconds;
struct tm *localTimePtr = localtime(&time);
char date[30];
strftime(date, sizeof(date), "%Y-%m-%d at %T", localTimePtr);
@@ -213,27 +254,31 @@ namespace dchat
}
// Max avatar size = 1mb
- const ContentByUrlResult avatarResult = cache.getContentByUrl(message->user->avatarUrl, 1024 * 1024);
+ const ContentByUrlResult avatarResult = cache->getContentByUrl(message->roomMessage->creator->avatarUrl, 1024 * 1024);
if(avatarResult.type == ContentByUrlResult::Type::CACHED)
{
circleShader->setUniform("texture", sf::Shader::CurrentTexture);
- if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE)
+ if(avatarResult.cachedType == ContentByUrlResult::CachedType::STATIC_IMAGE)
{
+ auto *staticImage = static_cast<SfmlStaticImage*>(avatarResult.staticImage);
// TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
- sf::Sprite sprite(*avatarResult.texture);
- auto textureSize = avatarResult.texture->getSize();
+ sf::Sprite sprite(staticImage->texture);
+ auto textureSize = staticImage->texture.getSize();
sprite.setPosition(sf::Vector2f(startX, floor(position.y)));
sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y));
window.draw(sprite, circleShader);
}
else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF)
{
- auto gifSize = avatarResult.gif->getSize();
- avatarResult.gif->setPosition(sf::Vector2f(startX, floor(position.y)));
- avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
- avatarResult.gif->setColor(sf::Color::White);
- avatarResult.gif->draw(window, circleShader);
+ auto *gif = static_cast<SfmlGif*>(avatarResult.gif);
+ gif->update();
+ auto gifSize = gif->getSize();
+ sf::Sprite sprite(gif->texture);
+ sprite.setPosition(sf::Vector2f(startX, floor(position.y)));
+ sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
+ sprite.setColor(sf::Color::White);
+ window.draw(sprite, circleShader);
}
}
else
@@ -280,7 +325,7 @@ namespace dchat
totalHeight = (position.y - scroll) - startHeight;
}
- void MessageBoard::drawSimple(sf::RenderWindow &window, Cache &cache)
+ void MessageBoard::drawSimple(sf::RenderWindow &window, Cache *cache)
{
const float LINE_SPACING = 20.0f * Settings::getScaling();
const float MESSAGE_PADDING_TOP = 0.0f;
@@ -295,7 +340,7 @@ namespace dchat
const int timestampTextCharacterSize = 15 * Settings::getScaling();
const float timestampTextHeight = timestampFont->getLineSpacing(timestampTextCharacterSize);
- sf::Vector2<double> position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling() + usernameMaxWidth, ChannelTopPanel::getHeight() + PADDING_TOP);
+ sf::Vector2<double> position(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling() + usernameMaxWidth, RoomTopPanel::getHeight() + PADDING_TOP);
double startHeight = position.y;
position.y += scroll;
usize numMessages = messages.size();
@@ -305,16 +350,16 @@ namespace dchat
position.y += (MESSAGE_PADDING_TOP * Settings::getScaling());
if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y)
{
- sf::String usernameTextStr = sf::String::fromUtf8(message->user->getName().begin(), message->user->getName().end());
+ sf::String usernameTextStr = sf::String::fromUtf8(message->roomMessage->creator->nickname.begin(), message->roomMessage->creator->nickname.end());
usernameTextStr += " - ";
sf::Text usernameText(usernameTextStr, *usernameFont, usernameTextCharacterSize);
usernameText.setFillColor(sf::Color(15, 192, 252));
usernameText.setPosition(sf::Vector2f(floor(position.x - usernameText.getLocalBounds().width), floor(position.y)));
window.draw(usernameText);
- if(message->timestampSeconds)
+ if(message->roomMessage->timestampSeconds)
{
- time_t time = (time_t)message->timestampSeconds;
+ time_t time = (time_t)message->roomMessage->timestampSeconds;
struct tm *localTimePtr = localtime(&time);
char date[30];
strftime(date, sizeof(date), "%Y-%m-%d at %T", localTimePtr);
@@ -337,13 +382,13 @@ namespace dchat
totalHeight = (position.y - scroll) - startHeight;
}
- void MessageBoard::processEvent(const sf::Event &event, Cache &cache)
+ void MessageBoard::processEvent(const sf::Event &event, Cache *cache)
{
lock_guard<mutex> lock(messageProcessMutex);
- OnlineLocalUser *onlineLocalUser = nullptr;
- if(channel->getLocalUser()->type == User::Type::ONLINE_LOCAL_USER)
- onlineLocalUser = static_cast<OnlineLocalUser*>(channel->getLocalUser());
+ std::shared_ptr<User> onlineLocalUser = nullptr;
+ if(room)
+ onlineLocalUser = room->localUser;
bool openContextMenu = false;
if(onlineLocalUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right)
@@ -358,14 +403,15 @@ namespace dchat
message->text.processEvent(event, cache);
auto textPos = message->text.getPosition();
- if(openContextMenu && message->user == channel->getLocalUser() && event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + message->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + message->text.getHeight())
+ if(openContextMenu && message->roomMessage->creator == onlineLocalUser && event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + message->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + message->text.getHeight())
{
auto contextMenu = GlobalContextMenu::getEditMessageContextMenu();
contextMenu->setPosition(sf::Vector2f(event.mouseButton.x, event.mouseButton.y));
contextMenu->setVisible(true);
- GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, message, onlineLocalUser](ContextMenuItem *menuItem)
+ GlobalContextMenu::setClickDeleteMessageCallbackFunc([](ContextMenuItem *menuItem)
{
- channel->deleteMessage(message->id, onlineLocalUser->getPublicKey());
+ // TODO: Add: room->deleteMessage();
+ //channel->deleteMessage(message->id, onlineLocalUser->getPublicKey());
GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr);
});
}
@@ -382,12 +428,12 @@ namespace dchat
}
}
- void MessageBoard::draw(sf::RenderWindow &window, Cache &cache)
+ void MessageBoard::draw(sf::RenderWindow &window, Cache *cache)
{
auto windowSize = window.getSize();
- backgroundSizeWithoutPadding = sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth()), floor(windowSize.y - ChannelTopPanel::getHeight() - Chatbar::getHeight()));
- backgroundSize = sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(windowSize.y - ChannelTopPanel::getHeight() - Chatbar::getHeight() - PADDING_TOP));
- backgroundPos = sf::Vector2f(ChannelSidePanel::getWidth(), ChannelTopPanel::getHeight());
+ backgroundSizeWithoutPadding = sf::Vector2f(floor(windowSize.x - RoomSidePanel::getWidth()), floor(windowSize.y - RoomTopPanel::getHeight() - Chatbar::getHeight()));
+ backgroundSize = sf::Vector2f(floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(windowSize.y - RoomTopPanel::getHeight() - Chatbar::getHeight() - PADDING_TOP));
+ backgroundPos = sf::Vector2f(RoomSidePanel::getWidth(), RoomTopPanel::getHeight());
//if(backgroundSize != staticContentTexture.getSize())
// updateStaticContentTexture(backgroundSize);
@@ -399,7 +445,7 @@ namespace dchat
sf::RectangleShape backgroundRect(backgroundSizeWithoutPadding);
backgroundRect.setFillColor(ColorScheme::getBackgroundColor());
- backgroundRect.setPosition(ChannelSidePanel::getWidth(), ChannelTopPanel::getHeight());
+ backgroundRect.setPosition(RoomSidePanel::getWidth(), RoomTopPanel::getHeight());
window.draw(backgroundRect);
double deltaTimeMicro = (double)frameTimer.getElapsedTime().asMicroseconds();
@@ -472,16 +518,4 @@ namespace dchat
//textureSprite.setPosition(backgroundPos);
//window.draw(textureSprite);
}
-
- Message* MessageBoard::getLatestMessage()
- {
- if(!messages.empty())
- return messages.back();
- return nullptr;
- }
-
- const std::vector<Message*>& MessageBoard::getMessages() const
- {
- return messages;
- }
}
diff --git a/src/ResourceCache.cpp b/src/ResourceCache.cpp
index f6f8fad..b56a99d 100644
--- a/src/ResourceCache.cpp
+++ b/src/ResourceCache.cpp
@@ -1,14 +1,18 @@
#include "../include/ResourceCache.hpp"
+#include "../include/Gif.hpp"
+#include "../include/StaticImage.hpp"
+#include "../include/WebPagePreview.hpp"
#include <unordered_map>
-#include <gd.h>
using namespace std;
namespace dchat
{
- unordered_map<string, sf::Font*> fonts;
- unordered_map<string, sf::Texture*> textures;
- unordered_map<string, sf::Shader*> shaders;
+ static unordered_map<string, sf::Font*> fonts;
+ static unordered_map<string, sf::Texture*> textures;
+ static unordered_map<string, sf::Shader*> shaders;
+
+ static Cache *cache = nullptr;
const sf::Font* ResourceCache::getFont(const string &filepath)
{
@@ -29,50 +33,6 @@ namespace dchat
return font;
}
- sf::Texture* ResourceCache::getTexture(const string &filepath)
- {
- auto it = textures.find(filepath);
- if(it != textures.end())
- return it->second;
-
- gdImagePtr imgPtr = gdImageCreateFromFile(filepath.c_str());
- if(!imgPtr)
- {
- string errMsg = "Failed to load texture with gd: ";
- errMsg += filepath;
- throw FailedToLoadResourceException(errMsg);
- }
-
- gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED);
- gdImagePtr newImgPtr = gdImageScale(imgPtr, 100, 100);
- if(!newImgPtr)
- {
- gdImageDestroy(imgPtr);
- string errMsg = "Failed to scale image with gd: ";
- errMsg += filepath;
- throw FailedToLoadResourceException(errMsg);
- }
-
- gdImageFile(newImgPtr, filepath.c_str());
-
- gdImageDestroy(imgPtr);
- gdImageDestroy(newImgPtr);
-
- sf::Texture *texture = new sf::Texture();
- if(!texture->loadFromFile(filepath))
- {
- delete texture;
- string errMsg = "Failed to load texture: ";
- errMsg += filepath;
- throw FailedToLoadResourceException(errMsg);
- }
-
- texture->setSmooth(true);
- texture->generateMipmap();
- textures[filepath] = texture;
- return texture;
- }
-
sf::Shader* ResourceCache::getShader(const std::string &filepath, sf::Shader::Type shaderType)
{
auto it = shaders.find(filepath);
@@ -91,4 +51,29 @@ namespace dchat
shaders[filepath] = shader;
return shader;
}
+
+ Cache* ResourceCache::getCache()
+ {
+ if(!cache) {
+ CreateGifFunc createGifFunc = [](StringView fileContent)
+ {
+ return new SfmlGif(fileContent);
+ };
+
+ CreateStaticImageFunc createStaticImageFunc = [](const boost::filesystem::path &filepath)
+ {
+ return new SfmlStaticImage(filepath);
+ };
+
+ CreateWebPagePreviewFunc createWebPagePreview = [](const std::string &title, const std::string &description)
+ {
+ return new SfmlWebPagePreview(title, description);
+ };
+
+ cache = new Cache(std::move(createGifFunc),
+ std::move(createStaticImageFunc),
+ std::move(createWebPagePreview));
+ }
+ return cache;
+ }
}
diff --git a/src/Room.cpp b/src/Room.cpp
new file mode 100644
index 0000000..abcc16b
--- /dev/null
+++ b/src/Room.cpp
@@ -0,0 +1,27 @@
+#include "../include/Room.hpp"
+
+namespace dchat
+{
+ static std::shared_ptr<Room> currentRoom = nullptr;
+ static std::shared_ptr<Rooms> rooms = nullptr;
+
+ std::shared_ptr<Room> getCurrentRoom()
+ {
+ return currentRoom;
+ }
+
+ void setCurrentRoom(std::shared_ptr<Room> room)
+ {
+ currentRoom = room;
+ }
+
+ std::shared_ptr<Rooms> getRooms()
+ {
+ return rooms;
+ }
+
+ void setRooms(std::shared_ptr<Rooms> _rooms)
+ {
+ rooms = _rooms;
+ }
+} \ No newline at end of file
diff --git a/src/RoomContainer.cpp b/src/RoomContainer.cpp
new file mode 100644
index 0000000..c560688
--- /dev/null
+++ b/src/RoomContainer.cpp
@@ -0,0 +1,24 @@
+#include "../include/RoomContainer.hpp"
+
+namespace dchat
+{
+ RoomContainer::RoomContainer(std::shared_ptr<Room> _room) :
+ room(_room),
+ messageBoard(_room),
+ offlineRoom(false)
+ {
+
+ }
+
+ void RoomContainer::processEvent(const sf::Event &event, Cache *cache)
+ {
+ chatbar.processEvent(event, cache, room, &messageBoard);
+ messageBoard.processEvent(event, cache);
+ }
+
+ void RoomContainer::draw(sf::RenderWindow &window, Cache *cache)
+ {
+ messageBoard.draw(window, cache);
+ chatbar.draw(window, cache);
+ }
+} \ No newline at end of file
diff --git a/src/ChannelSidePanel.cpp b/src/RoomSidePanel.cpp
index 49407cc..a7baf8a 100644
--- a/src/ChannelSidePanel.cpp
+++ b/src/RoomSidePanel.cpp
@@ -1,11 +1,13 @@
-#include "../include/ChannelSidePanel.hpp"
-#include "../include/ChannelTopPanel.hpp"
+#include "../include/RoomSidePanel.hpp"
+#include "../include/RoomTopPanel.hpp"
#include "../include/ResourceCache.hpp"
#include "../include/Settings.hpp"
-#include "../include/Channel.hpp"
#include "../include/ColorScheme.hpp"
+#include "../include/Room.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/Text.hpp>
+#include <SFML/Window/Mouse.hpp>
+#include <dchat/Room.hpp>
#include <vector>
#include <cmath>
@@ -13,26 +15,18 @@ using namespace std;
namespace dchat
{
- static vector<Channel*> channels;
static sf::Vector2f position;
const float WIDTH = 300.0f;
const unsigned int FONT_SIZE = 20;
const float PADDING_BOTTOM = 10.0f;
const float CHANNEL_NAME_BOX_HEIGHT_RATIO = 1.5f;
-
- void ChannelSidePanel::addChannel(Channel *channel)
- {
- channels.push_back(channel);
- }
-
- void ChannelSidePanel::removeAllChannels()
- {
- channels.clear();
- }
-
- void ChannelSidePanel::draw(sf::RenderWindow &window)
+
+ void RoomSidePanel::draw(sf::RenderWindow &window)
{
- float posY = ChannelTopPanel::getHeight();
+ std::shared_ptr<Rooms> rooms = getRooms();
+ if(!rooms) return;
+
+ float posY = RoomTopPanel::getHeight();
auto windowSize = window.getSize();
sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y));
rect.setPosition(0.0f, 0.0f);
@@ -47,10 +41,11 @@ namespace dchat
auto mousePos = sf::Mouse::getPosition(window);
position.y = posY;
- for(Channel *channel : channels)
+ for(auto &it : rooms->getRooms())
{
+ const std::shared_ptr<Room> room = it.second;
sf::FloatRect box(0.0f, position.y, getWidth(), channelBoxHeight);
- if(channel == Channel::getCurrent())
+ if(room == getCurrentRoom())
{
rect.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(15, 15, 15));
rect.setSize(sf::Vector2f(box.width, box.height));
@@ -66,13 +61,13 @@ namespace dchat
if(sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))
{
- Channel::setCurrent(channel);
+ setCurrentRoom(room);
}
}
// TODO: Remove this shit
sf::String str = "# ";
- str += sf::String::fromUtf8(channel->getName().begin(), channel->getName().end());
+ str += sf::String::fromUtf8(room->name.begin(), room->name.end());
sf::Text text(str, *font, fontSize);
text.setPosition(sf::Vector2f(position.x, floor(position.y + channelBoxHeight * 0.5f - fontHeight * 0.5f)));
text.setFillColor(ColorScheme::getTextRegularColor());
@@ -81,12 +76,12 @@ namespace dchat
}
}
- float ChannelSidePanel::getWidth()
+ float RoomSidePanel::getWidth()
{
return floor(WIDTH * Settings::getScaling());
}
- float ChannelSidePanel::getHeight()
+ float RoomSidePanel::getHeight()
{
return position.y;
}
diff --git a/src/ChannelTopPanel.cpp b/src/RoomTopPanel.cpp
index ec02651..f46bc33 100644
--- a/src/ChannelTopPanel.cpp
+++ b/src/RoomTopPanel.cpp
@@ -1,12 +1,13 @@
-#include "../include/ChannelTopPanel.hpp"
+#include "../include/RoomTopPanel.hpp"
#include "../include/Settings.hpp"
#include "../include/ResourceCache.hpp"
-#include "../include/Channel.hpp"
#include "../include/ColorScheme.hpp"
-#include "../include/ChannelSidePanel.hpp"
+#include "../include//RoomSidePanel.hpp"
#include "../include/UsersSidePanel.hpp"
+#include "../include/Room.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/Text.hpp>
+#include <dchat/Room.hpp>
#include <cmath>
namespace dchat
@@ -39,38 +40,38 @@ namespace dchat
window.draw(rectangle, 8, sf::Quads);
}
- void ChannelTopPanel::draw(sf::RenderWindow &window)
+ void RoomTopPanel::draw(sf::RenderWindow &window)
{
const sf::Color lineSideColor = ColorScheme::getBackgroundColor() + sf::Color(20, 0, 0);
const sf::Color lineCenterColor = lineSideColor + sf::Color(40, 0, 0);
auto windowSize = window.getSize();
- sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT));
- rect.setPosition(ChannelSidePanel::getWidth(), 0.0f);
+ sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - RoomSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT));
+ rect.setPosition(RoomSidePanel::getWidth(), 0.0f);
rect.setFillColor(ColorScheme::getBackgroundColor());
window.draw(rect);
- sf::Vector2f bottomLinePos(floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), getHeight() - BOTTOM_LINE_HEIGHT);
- sf::Vector2f bottomLineSize(floor(windowSize.x - ChannelSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), BOTTOM_LINE_HEIGHT);
+ sf::Vector2f bottomLinePos(floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), getHeight() - BOTTOM_LINE_HEIGHT);
+ sf::Vector2f bottomLineSize(floor(windowSize.x - RoomSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), BOTTOM_LINE_HEIGHT);
LineColor lineColor;
lineColor.sideColor = lineSideColor;
lineColor.centerColor = lineCenterColor;
drawGradientLine(bottomLinePos, bottomLineSize, lineColor, window);
- Channel *currentChannel = Channel::getCurrent();
- if(!currentChannel) return;
+ std::shared_ptr<Room> currentRoom = getCurrentRoom();
+ if(!currentRoom) return;
sf::String str = "# ";
- str += sf::String::fromUtf8(currentChannel->getName().begin(), currentChannel->getName().end());
+ str += sf::String::fromUtf8(currentRoom->name.begin(), currentRoom->name.end());
float fontSize = FONT_SIZE * Settings::getScaling();
sf::Text text(str, *ResourceCache::getFont("fonts/Nunito-Regular.ttf"), fontSize);
- text.setPosition(floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), PADDING_TOP);
+ text.setPosition(floor(RoomSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), PADDING_TOP);
text.setFillColor(ColorScheme::getTextRegularColor());
window.draw(text);
}
- float ChannelTopPanel::getHeight()
+ float RoomTopPanel::getHeight()
{
float fontSize = FONT_SIZE * Settings::getScaling();
return floor(ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize) + PADDING_TOP + PADDING_BOTTOM + BOTTOM_LINE_HEIGHT);
diff --git a/src/Rpc.cpp b/src/Rpc.cpp
deleted file mode 100644
index 2bf54fb..0000000
--- a/src/Rpc.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include "../include/Rpc.hpp"
-#include <string>
-#include <cassert>
-
-namespace dchat
-{
- Rpc::Rpc(u16 port) :
- context(1),
- socket(context, ZMQ_PAIR)
- {
- std::string addr = "tcp://*:";
- addr += std::to_string(port);
- socket.bind(addr);
- }
-
- void Rpc::recv(RpcRecvCallbackFunc recvCallbackFunc)
- {
- assert(recvCallbackFunc);
- zmq::message_t request;
- if(socket.recv(&request, ZMQ_NOBLOCK))
- {
- recvCallbackFunc(&request);
- }
- }
-
- bool Rpc::send(const void *data, const usize size)
- {
- if(size == 0) return false;
- try
- {
- return socket.send(data, size, ZMQ_NOBLOCK) > 0;
- }
- catch(zmq::error_t &e)
- {
- fprintf(stderr, "Rpc::send failed, reason: %s\n", e.what());
- return false;
- }
- }
-} \ No newline at end of file
diff --git a/src/StaticImage.cpp b/src/StaticImage.cpp
new file mode 100644
index 0000000..8d5bd63
--- /dev/null
+++ b/src/StaticImage.cpp
@@ -0,0 +1,10 @@
+#include "../include/StaticImage.hpp"
+
+namespace dchat
+{
+ SfmlStaticImage::SfmlStaticImage(const boost::filesystem::path &path)
+ {
+ if(!texture.loadFromFile(path.string()))
+ fprintf(stderr, "Failed to create texture for static image!\n");
+ }
+} \ No newline at end of file
diff --git a/src/Suggestions.cpp b/src/Suggestions.cpp
index 113cd7e..2fbbd72 100644
--- a/src/Suggestions.cpp
+++ b/src/Suggestions.cpp
@@ -25,7 +25,7 @@ namespace dchat
}
}
- void Suggestions::draw(sf::RenderWindow &window, Cache &cache)
+ void Suggestions::draw(sf::RenderWindow &window, Cache *cache)
{
if(texts.empty()) return;
diff --git a/src/Text.cpp b/src/Text.cpp
index 39e339f..6e3aac9 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -1,12 +1,15 @@
#include "../include/Text.hpp"
-#include "../include/Cache.hpp"
-#include "../include/Gif.hpp"
-#include "../include/WebPagePreview.hpp"
#include "../include/ColorScheme.hpp"
#include "../include/ImagePreview.hpp"
-#include "../include/StringUtils.hpp"
+#include "../include/StaticImage.hpp"
+#include "../include/Gif.hpp"
+#include "../include/WebPagePreview.hpp"
+#include "../include/ResourceCache.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Window/Clipboard.hpp>
+#include <dchat/Gif.hpp>
+#include <dchat/WebPagePreview.hpp>
+#include <dchat/Process.hpp>
#include <cmath>
namespace dchat
@@ -718,75 +721,79 @@ namespace dchat
return static_cast<int>(1.0f + position.y / (vspace + lineSpacing));
}
- void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache)
+ void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache *cache)
{
if(event.button != sf::Mouse::Button::Left) return;
float vspace = font->getLineSpacing(characterSize);
-
+
+ sf::Vector2f pos = position;
+ pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left
+ if(pos.y + getHeight() <= 0.0f || pos.y >= renderTargetSize.y)
+ return;
+
for(TextElement &textElement : textElements)
{
- if(textElement.type == TextElement::Type::URL)
+ if(textElement.type != TextElement::Type::URL)
+ continue;
+
+ sf::Vector2f pos = position;
+ pos.y += floor(textElement.position.y);
+ float imageHeight = floor(vspace * IMAGE_HEIGHT_SCALE);
+
+ // TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key)
+ auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8();
+ const std::string &utf8Str = *(std::basic_string<char>*)&u8Str;
+ const ContentByUrlResult contentByUrlResult = cache->getContentByUrl(utf8Str);
+ if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
- sf::Vector2f pos = position;
- pos.y += floor(textElement.position.y);
- float imageHeight = floor(vspace * IMAGE_HEIGHT_SCALE);
-
- // TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key)
- auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8();
- const std::string &utf8Str = *(std::basic_string<char>*)&u8Str;
- const ContentByUrlResult contentByUrlResult = cache.getContentByUrl(utf8Str);
- if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
+ switch(contentByUrlResult.cachedType)
{
- switch(contentByUrlResult.cachedType)
+ case ContentByUrlResult::CachedType::STATIC_IMAGE:
{
- case ContentByUrlResult::CachedType::TEXTURE:
+ auto *staticImage = static_cast<SfmlStaticImage*>(contentByUrlResult.staticImage);
+ auto textureSize = staticImage->texture.getSize();
+ float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
+ float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
+ if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
{
- auto textureSize = contentByUrlResult.texture->getSize();
- float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
- float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
- if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
- {
- ImagePreview::preview(contentByUrlResult.texture, utf8Str);
- return;
- }
- break;
+ ImagePreview::preview(&staticImage->texture, utf8Str);
+ return;
}
- case ContentByUrlResult::CachedType::GIF:
+ break;
+ }
+ case ContentByUrlResult::CachedType::GIF:
+ {
+ auto textureSize = contentByUrlResult.gif->getSize();
+ float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
+ float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
+ if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
{
- auto textureSize = contentByUrlResult.gif->getSize();
- float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
- float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
- if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
- {
- ImagePreview::preview(contentByUrlResult.gif, utf8Str);
- return;
- }
- break;
+ ImagePreview::preview(contentByUrlResult.gif, utf8Str);
+ return;
}
- case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
+ break;
+ }
+ case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
+ {
+ const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f));
+ if(event.x >= pos.x && event.x <= pos.x + previewWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
{
- const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f));
- if(event.x >= pos.x && event.x <= pos.x + previewWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
- {
- // TODO: Implement for other platforms than linux
- std::string escapedUrl = stringReplaceChar(utf8Str, "'", "");
- escapedUrl = stringReplaceChar(escapedUrl, "\\", "");
- std::string cmd = "xdg-open '";
- cmd += escapedUrl;
- cmd += "'";
- printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str());
- system(cmd.c_str());
- return;
- }
- break;
+ // TODO: Implement for other platforms than linux
+ std::string cmd = "xdg-open '";
+ cmd += escapeCommand(utf8Str);
+ cmd += "'";
+ printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str());
+ system(cmd.c_str());
+ return;
}
+ break;
}
}
}
}
}
- void Text::processEvent(const sf::Event &event, Cache &cache)
+ void Text::processEvent(const sf::Event &event, Cache *cache)
{
if(event.type == sf::Event::MouseButtonReleased)
{
@@ -890,7 +897,7 @@ namespace dchat
}
}
- bool Text::draw(sf::RenderTarget &target, Cache &cache)
+ bool Text::draw(sf::RenderTarget &target, Cache *cache)
{
if(dirtyText)
{
@@ -923,7 +930,8 @@ namespace dchat
//sf::FloatRect textRect(pos.x, pos.y, maxWidth, )
//colRect.contains()
//if(pos.x + maxWidth <= 0.0f || pos.x >= maxWidth || pos.y + totalHeight <= 0.0f || pos.y >= target.getSize().y) return;
- if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y)
+ renderTargetSize = target.getSize();
+ if(pos.y + getHeight() <= 0.0f || pos.y >= renderTargetSize.y)
{
if(!editable && visible && lastSeenTimer.getElapsedTime().asMilliseconds() > 3000)
{
@@ -958,29 +966,33 @@ namespace dchat
// TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key)
auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8();
const std::string &utf8Str = *(std::basic_string<char>*)&u8Str;
- const ContentByUrlResult contentByUrlResult = cache.getContentByUrl(utf8Str);
+ const ContentByUrlResult contentByUrlResult = cache->getContentByUrl(utf8Str);
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
switch(contentByUrlResult.cachedType)
{
case ContentByUrlResult::CachedType::GIF:
{
- auto gifSize = contentByUrlResult.gif->getSize();
+ auto *gif = static_cast<SfmlGif*>(contentByUrlResult.gif);
+ gif->update();
+ auto gifSize = gif->getSize();
float widthToHeightRatio = (float)gifSize.x / (float)gifSize.y;
-
- contentByUrlResult.gif->setPosition(pos);
- contentByUrlResult.gif->setScale(sf::Vector2f(size.x / (float)gifSize.x * widthToHeightRatio, size.y / (float)gifSize.y));
- contentByUrlResult.gif->setColor(sf::Color::White);
- contentByUrlResult.gif->draw(target);
+
+ sf::Sprite sprite(gif->texture);
+ sprite.setPosition(pos);
+ sprite.setScale(sf::Vector2f(size.x / (float)gifSize.x * widthToHeightRatio, size.y / (float)gifSize.y));
+ sprite.setColor(sf::Color::White);
+ target.draw(sprite);
break;
}
- case ContentByUrlResult::CachedType::TEXTURE:
+ case ContentByUrlResult::CachedType::STATIC_IMAGE:
{
- auto textureSize = contentByUrlResult.texture->getSize();
+ auto *staticImage = static_cast<SfmlStaticImage*>(contentByUrlResult.staticImage);
+ auto textureSize = staticImage->texture.getSize();
float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
// TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
- sf::Sprite sprite(*contentByUrlResult.texture);
+ sf::Sprite sprite(staticImage->texture);
sprite.setPosition(pos);
sprite.setScale(size.x / (float)textureSize.x * widthToHeightRatio, size.y / (float)textureSize.y);
target.draw(sprite);
@@ -1008,29 +1020,33 @@ namespace dchat
// TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key)
auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8();
const std::string &utf8Str = *(std::basic_string<char>*)&u8Str;
- const ContentByUrlResult contentByUrlResult = cache.getContentByUrl(utf8Str);
+ const ContentByUrlResult contentByUrlResult = cache->getContentByUrl(utf8Str);
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
switch(contentByUrlResult.cachedType)
{
case ContentByUrlResult::CachedType::GIF:
{
- auto gifSize = contentByUrlResult.gif->getSize();
+ auto *gif = static_cast<SfmlGif*>(contentByUrlResult.gif);
+ gif->update();
+ auto gifSize = gif->getSize();
float widthToHeightRatio = (float)gifSize.x / (float)gifSize.y;
- contentByUrlResult.gif->setPosition(pos);
- contentByUrlResult.gif->setScale(sf::Vector2f(imageHeight / (float)gifSize.x * widthToHeightRatio, imageHeight / (float)gifSize.y));
- contentByUrlResult.gif->setColor(sf::Color::White);
- contentByUrlResult.gif->draw(target);
+ sf::Sprite sprite(gif->texture);
+ sprite.setPosition(pos);
+ sprite.setScale(sf::Vector2f(imageHeight / (float)gifSize.x * widthToHeightRatio, imageHeight / (float)gifSize.y));
+ sprite.setColor(sf::Color::White);
+ target.draw(sprite);
break;
}
- case ContentByUrlResult::CachedType::TEXTURE:
+ case ContentByUrlResult::CachedType::STATIC_IMAGE:
{
- auto textureSize = contentByUrlResult.texture->getSize();
+ auto *staticImage = static_cast<SfmlStaticImage*>(contentByUrlResult.staticImage);
+ auto textureSize = staticImage->texture.getSize();
float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
// TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
- sf::Sprite sprite(*contentByUrlResult.texture);
+ sf::Sprite sprite(staticImage->texture);
sprite.setPosition(pos);
sprite.setScale(imageHeight / (float)textureSize.x * widthToHeightRatio, imageHeight / (float)textureSize.y);
target.draw(sprite);
@@ -1038,23 +1054,24 @@ namespace dchat
}
case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
{
+ auto *webPagePreview = static_cast<SfmlWebPagePreview*>(contentByUrlResult.webPagePreview);
const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f));
- contentByUrlResult.webPagePreview->title.setCharacterSize(characterSize);
- contentByUrlResult.webPagePreview->title.setMaxWidth(previewWidth);
- contentByUrlResult.webPagePreview->title.setPosition(pos);
- contentByUrlResult.webPagePreview->title.setLineSpacing(0.0f);
- contentByUrlResult.webPagePreview->title.setFillColor(URL_COLOR);
- contentByUrlResult.webPagePreview->title.draw(target, cache);
+ webPagePreview->title.setCharacterSize(characterSize);
+ webPagePreview->title.setMaxWidth(previewWidth);
+ webPagePreview->title.setPosition(pos);
+ webPagePreview->title.setLineSpacing(0.0f);
+ webPagePreview->title.setFillColor(URL_COLOR);
+ webPagePreview->title.draw(target, cache);
- pos.y += contentByUrlResult.webPagePreview->title.getHeight();
+ pos.y += webPagePreview->title.getHeight();
- contentByUrlResult.webPagePreview->description.setCharacterSize(characterSize);
- contentByUrlResult.webPagePreview->description.setMaxWidth(previewWidth);
- contentByUrlResult.webPagePreview->description.setPosition(pos);
- contentByUrlResult.webPagePreview->description.setLineSpacing(0.0f);
- contentByUrlResult.webPagePreview->description.setFillColor(color);
- contentByUrlResult.webPagePreview->description.draw(target, cache);
+ webPagePreview->description.setCharacterSize(characterSize);
+ webPagePreview->description.setMaxWidth(previewWidth);
+ webPagePreview->description.setPosition(pos);
+ webPagePreview->description.setLineSpacing(0.0f);
+ webPagePreview->description.setFillColor(color);
+ webPagePreview->description.draw(target, cache);
break;
}
}
diff --git a/src/User.cpp b/src/User.cpp
deleted file mode 100644
index 510f884..0000000
--- a/src/User.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-#include "../include/User.hpp"
-#include <cassert>
-
-namespace dchat
-{
- const static std::string SYSTEM_USER_NAME = "System";
- const i64 USER_TIMEOUT_SEC = 25;
-
- User::User(Type _type) :
- type(_type)
- {
-
- }
-
- OnlineUser::OnlineUser(const std::string &_name, Type type) :
- User(type),
- name(_name),
- pingTimestampSec(0)
- {
-
- }
-
- const std::string& OnlineUser::getName() const
- {
- return name;
- }
-
- bool OnlineUser::isConnected(i64 timestampUtcSec) const
- {
- i64 pingTimeDiffSec = timestampUtcSec - (i64)pingTimestampSec;
- return pingTimeDiffSec <= USER_TIMEOUT_SEC;
- }
-
- OnlineRemoteUser::OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &_publicKey) :
- OnlineUser(name, Type::ONLINE_REMOTE_USER),
- publicKey(_publicKey)
- {
-
- }
-
- const odhtdb::Signature::PublicKey& OnlineRemoteUser::getPublicKey() const
- {
- return publicKey;
- }
-
- OnlineLocalUser::OnlineLocalUser(const std::string &name, const odhtdb::Signature::KeyPair &_keyPair) :
- OnlineUser(name, Type::ONLINE_LOCAL_USER),
- keyPair(_keyPair)
- {
-
- }
-
- const odhtdb::Signature::PublicKey& OnlineLocalUser::getPublicKey() const
- {
- return keyPair.getPublicKey();
- }
-
- OnlineDiscordUser::OnlineDiscordUser(const std::string &discordUserName, u64 _discordUserId, User *_bridgeOwner) :
- OnlineUser(discordUserName, Type::ONLINE_DISCORD_USER),
- discordUserId(_discordUserId),
- bridgeOwner(_bridgeOwner)
- {
- assert(bridgeOwner);
- }
-
- const odhtdb::Signature::PublicKey& OnlineDiscordUser::getPublicKey() const
- {
- return odhtdb::Signature::PublicKey::ZERO;
- }
-
- OfflineUser::OfflineUser(const std::string &_name) :
- User(Type::OFFLINE),
- name(_name)
- {
-
- }
-
- const std::string& OfflineUser::getName() const
- {
- return name;
- }
-
- SystemUser::SystemUser() :
- User(Type::SYSTEM)
- {
-
- }
-
- const std::string& SystemUser::getName() const
- {
- return SYSTEM_USER_NAME;
- }
-}
diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp
index 7d7970f..39321d5 100644
--- a/src/UsersSidePanel.cpp
+++ b/src/UsersSidePanel.cpp
@@ -1,15 +1,17 @@
#include "../include/UsersSidePanel.hpp"
-#include "../include/ChannelTopPanel.hpp"
-#include "../include/ChannelSidePanel.hpp"
+#include "../include/RoomTopPanel.hpp"
+#include "../include/RoomSidePanel.hpp"
#include "../include/ResourceCache.hpp"
+#include "../include/StaticImage.hpp"
+#include "../include/Gif.hpp"
#include "../include/Settings.hpp"
-#include "../include/Channel.hpp"
#include "../include/ColorScheme.hpp"
-#include "../include/Gif.hpp"
+#include "../include/Room.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Text.hpp>
+#include <dchat/Room.hpp>
#include <vector>
#include <cmath>
#include <sibs/Functional.hpp>
@@ -26,21 +28,22 @@ namespace dchat
const float PADDING_BOTTOM = 20.0f;
const i64 USER_TIMEOUT_SEC = 25;
- static void renderUser(Cache &cache, const User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline)
+ static void renderUser(Cache *cache, const User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline)
{
if(position.y + AVATAR_DIAMETER > 0.0f && position.y < window.getSize().y)
{
// Max avatar size = 1mb
- const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024);
+ const ContentByUrlResult avatarResult = cache->getContentByUrl(user->avatarUrl, 1024 * 1024);
if(avatarResult.type == ContentByUrlResult::Type::CACHED)
{
circleShader->setUniform("texture", sf::Shader::CurrentTexture);
- if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE)
+ if(avatarResult.cachedType == ContentByUrlResult::CachedType::STATIC_IMAGE)
{
+ auto *staticImage = static_cast<SfmlStaticImage*>(avatarResult.staticImage);
// TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame
- sf::Sprite sprite(*avatarResult.texture);
- auto textureSize = avatarResult.texture->getSize();
+ sf::Sprite sprite(staticImage->texture);
+ auto textureSize = staticImage->texture.getSize();
sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y));
sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
@@ -48,11 +51,15 @@ namespace dchat
}
else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF)
{
- auto gifSize = avatarResult.gif->getSize();
- avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
- avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
- avatarResult.gif->setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
- avatarResult.gif->draw(window, circleShader);
+ auto *gif = static_cast<SfmlGif*>(avatarResult.gif);
+ gif->update();
+ auto gifSize = gif->getSize();
+
+ sf::Sprite sprite(gif->texture);
+ sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y)));
+ sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y));
+ sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100));
+ window.draw(sprite, circleShader);
}
}
else
@@ -64,7 +71,7 @@ namespace dchat
}
// TODO: Remove this shit
- sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end());
+ sf::String str = sf::String::fromUtf8(user->nickname.begin(), user->nickname.end());
sf::Text text(str, *font, FONT_SIZE * Settings::getScaling());
text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f));
text.setFillColor(isUserOnline ? sf::Color(15, 192, 252) : sf::Color(15, 192, 252, 100));
@@ -73,36 +80,38 @@ namespace dchat
position.y += ((AVATAR_DIAMETER + PADDING_BOTTOM) * Settings::getScaling());
}
- void UsersSidePanel::draw(sf::RenderWindow &window, Cache &cache)
+ void UsersSidePanel::draw(sf::RenderWindow &window, Cache *cache)
{
auto windowSize = window.getSize();
- float posY = std::min((windowSize.y - ChannelTopPanel::getHeight()) * 0.5f, ChannelSidePanel::getHeight());
+ float posY = std::min((windowSize.y - RoomTopPanel::getHeight()) * 0.5f, RoomSidePanel::getHeight());
- Channel *currentChannel = Channel::getCurrent();
- if(!currentChannel) return;
+ std::shared_ptr<Room> currentRoom = getCurrentRoom();
+ if(!currentRoom) return;
const sf::Font *font = ResourceCache::getFont("fonts/Nunito-Regular.ttf");
sf::Vector2f position(10.0f, posY);
const float textHeight = font->getLineSpacing(FONT_SIZE * Settings::getScaling());
- i64 timestampSec = currentChannel->getSyncedTimestampUtcInSec();
- auto &channelUsers = currentChannel->getUsers();
- auto currentChannelUsers = sibs::makeFunction(channelUsers.data(), channelUsers.data() + channelUsers.size());
- for(BridgeService *bridgeService : currentChannel->getBridgeServices())
- {
- auto &bridgeServiceUsers = bridgeService->getUsers();
- currentChannelUsers.merge(sibs::makeFunction(bridgeServiceUsers.data(), bridgeServiceUsers.data() + bridgeServiceUsers.size()));
- }
+ //i64 timestampSec = currentRoom->getSyncedTimestampUtcInSec();
+ auto &channelUsers = currentRoom->getUsers();
+ // auto currentChannelUsers = sibs::makeFunction(channelUsers.data(), channelUsers.data() + channelUsers.size());
+ // for(BridgeService *bridgeService : currentRoom->getBridgeServices())
+ // {
+ // auto &bridgeServiceUsers = bridgeService->getUsers();
+ // currentChannelUsers.merge(sibs::makeFunction(bridgeServiceUsers.data(), bridgeServiceUsers.data() + bridgeServiceUsers.size()));
+ // }
u32 numOnlineUsers = 0;
u32 numOfflineUsers = 0;
- for(const User *user : currentChannelUsers)
+ for(auto &it : channelUsers)
{
- if(user->isConnected(timestampSec))
- ++numOnlineUsers;
- else
- ++numOfflineUsers;
+ const User *user = it.second.get();
+ // TODO: Fix this
+ //if(user->isConnected(timestampSec))
+ // ++numOnlineUsers;
+ //else
+ // ++numOfflineUsers;
}
// TODO: Remove this shit
@@ -117,9 +126,11 @@ namespace dchat
sf::Shader *circleShader = ResourceCache::getShader("shaders/circleMask.glsl", sf::Shader::Fragment);
- for(const User *user : currentChannelUsers)
+ for(auto &it : channelUsers)
{
- if(user->isConnected(timestampSec))
+ const User *user = it.second.get();
+ // TODO: Fix this
+ //if(user->isConnected(timestampSec))
renderUser(cache, user, circleShader, window, position, font, textHeight, true);
}
@@ -136,11 +147,14 @@ namespace dchat
position.y += floor(font->getLineSpacing(text.getCharacterSize()));
position.y += PADDING_BOTTOM * Settings::getScaling() * 0.5f;
- for(const User *user : currentChannelUsers)
- {
- if(!user->isConnected(timestampSec))
- renderUser(cache, user, circleShader, window, position, font, textHeight, false);
- }
+ // TODO: Fix this
+ //for(const User *user : channelUsers)
+ //{
+ // const User *user = it.second.get();
+ // TODO: Fix this
+ //if(!user->isConnected(timestampSec))
+ // renderUser(cache, user, circleShader, window, position, font, textHeight, false);
+ // }
}
float UsersSidePanel::getWidth()
diff --git a/src/WebPagePreview.cpp b/src/WebPagePreview.cpp
index c7b3e7e..db58d97 100644
--- a/src/WebPagePreview.cpp
+++ b/src/WebPagePreview.cpp
@@ -1,12 +1,13 @@
#include "../include/WebPagePreview.hpp"
#include "../include/ResourceCache.hpp"
+#include "../include/Settings.hpp"
namespace dchat
{
- WebPagePreview::WebPagePreview(const sf::String &_title, const sf::String &_description) :
- title(_title, ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 10, 0),
- description(_description, ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 10, 0)
+ SfmlWebPagePreview::SfmlWebPagePreview(const std::string &_title, const std::string &_description) :
+ title(_title, ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 18.0f * Settings::getScaling(), 0),
+ description(_description, ResourceCache::getFont("fonts/Nunito-Regular.ttf"), 18.0f * Settings::getScaling(), 0)
{
}
-}
+} \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 15c02ea..1e2e6d9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,29 +1,22 @@
-#include "../include/Channel.hpp"
-#include "../include/ChannelSidePanel.hpp"
+#include "../include/RoomSidePanel.hpp"
#include "../include/UsersSidePanel.hpp"
-#include "../include/ChannelTopPanel.hpp"
-#include "../include/Cache.hpp"
+#include "../include/RoomTopPanel.hpp"
#include "../include/ResourceCache.hpp"
#include "../include/Video.hpp"
#include "../include/Command.hpp"
#include "../include/Settings.hpp"
#include "../include/ColorScheme.hpp"
#include "../include/GlobalContextMenu.hpp"
-#include "../include/StringUtils.hpp"
#include "../include/ImagePreview.hpp"
-#include "../include/Rpc.hpp"
-#include <msgpack.hpp>
+#include "../include/Room.hpp"
+#include "../include/RoomContainer.hpp"
#include <sstream>
#include <string>
#include <SFML/Graphics.hpp>
#include <cstring>
#include <boost/filesystem.hpp>
-#include <odhtdb/Database.hpp>
-#include <odhtdb/Signature.hpp>
-#include <odhtdb/bin2hex.hpp>
-#include <odhtdb/hex2bin.hpp>
-#include <ntp/NtpClient.hpp>
-#include <sibs/SafeSerializer.hpp>
+#include <dchat/Room.hpp>
+#include <dchat/Process.hpp>
#include <X11/Xlib.h>
#include <math.h>
@@ -33,25 +26,6 @@ using namespace dchat;
static bool focused = true;
static bool windowFocused = true;
-static void channelChangeUserNickname(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey)
-{
- auto user = channel->getUserByPublicKey(userPublicKey);
- if(!user)
- {
- fprintf(stderr, "Nickname change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str());
- return;
- }
-
- sibs::SafeDeserializer deserializer((const u8*)data.data, data.size);
- u8 nameLength = deserializer.extract<u8>();
- if(nameLength > 0)
- {
- user->name.resize(nameLength);
- deserializer.extract((u8*)&user->name[0], nameLength);
- }
- // We dont care if there is more data to read (malicious packet), we already got all the data we need
-}
-
static bool looksLikeUrl(const string &str)
{
if(str.find('.') == string::npos)
@@ -62,152 +36,13 @@ static bool looksLikeUrl(const string &str)
if(str.size() >= 8 && strncmp(str.c_str(), "https://", 8) == 0)
return true;
-
- return false;
-}
-
-static void channelChangeUserAvatar(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey)
-{
- auto user = channel->getUserByPublicKey(userPublicKey);
- if(!user)
- {
- fprintf(stderr, "Avatar change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str());
- return;
- }
-
- sibs::SafeDeserializer deserializer((const u8*)data.data, data.size);
- u16 avatarLength = deserializer.extract<u16>();
- if(avatarLength >= 10 && avatarLength <= 512)
- {
- string avatarUrl;
- avatarUrl.resize(avatarLength);
- deserializer.extract((u8*)&avatarUrl[0], avatarLength);
- if(looksLikeUrl(avatarUrl))
- {
- user->avatarUrl = move(avatarUrl);
- }
- }
- // We dont care if there is more data to read (malicious packet), we already got all the data we need
-}
-static void channelChangeChannelName(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey)
-{
- auto user = channel->getUserByPublicKey(userPublicKey);
- if(!user)
- {
- fprintf(stderr, "Channel change name: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str());
- return;
- }
-
- int userPermissionLevel = channel->getUserLowestPermissionLevel(user);
- if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN)
- {
- fprintf(stderr, "Channel change name: attempted by user %s who is not an admin (permission level: %d)\n", user->getName().c_str(), userPermissionLevel);
- return;
- }
+ if(str.size() >= 4 && strncmp(str.c_str(), "www.", 4) == 0)
+ return true;
- sibs::SafeDeserializer deserializer((const u8*)data.data, data.size);
- u16 channelNameLength = deserializer.extract<u16>();
- if(channelNameLength > 0 && channelNameLength <= 32)
- {
- string channelName;
- channelName.resize(channelNameLength);
- deserializer.extract((u8*)&channelName[0], channelNameLength);
- channel->setNameLocally(channelName);
- }
- // We dont care if there is more data to read (malicious packet), we already got all the data we need
+ return false;
}
-static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp, bool loadedFromCache)
-{
- User *user = channel->getUserByPublicKey(creatorPublicKey);
- if(!user)
- {
- fprintf(stderr, "Missing user? %s\n", creatorPublicKey.toString().c_str());
- return;
- }
-
- if(decryptedObject.size > 1)
- {
- auto channelDataType = (ChannelDataType)static_cast<const char*>(decryptedObject.data)[0];
- StringView decryptedData((const char*)decryptedObject.data + 1, decryptedObject.size - 1);
- switch(channelDataType)
- {
- case ChannelDataType::ADD_MESSAGE:
- {
- string msg(decryptedData.data, decryptedData.size);
- Message *latestMessage = channel->getLatestMessage();
- auto timestampSeconds = ntp::NtpTimestamp::fromCombined(timestamp).seconds;
- channel->addLocalMessage(msg, user, timestampSeconds, requestHash);
- if(!loadedFromCache && !windowFocused && channel->getLocalUser()->isOnlineUser() && (!latestMessage || (latestMessage && timestampSeconds >= latestMessage->timestampSeconds)))
- {
- auto onlineLocalUser = static_cast<OnlineLocalUser*>(channel->getLocalUser());
- if(creatorPublicKey != onlineLocalUser->getPublicKey())
- {
- stringReplaceChar(msg, "'", "");
- stringReplaceChar(msg, "\\", "");
- string cmd = "notify-send dchat '";
- cmd += msg;
- cmd += "'";
- system(cmd.c_str());
- }
- }
- break;
- }
- case ChannelDataType::DELETE_MESSAGE:
- {
- sibs::SafeDeserializer deserializer((const u8*)decryptedData.data, decryptedData.size);
- odhtdb::Hash messageId;
- deserializer.extract((u8*)messageId.getData(), odhtdb::HASH_BYTE_SIZE);
- channel->deleteLocalMessage(messageId, creatorPublicKey);
- break;
- }
- case ChannelDataType::NICKNAME_CHANGE:
- {
- try
- {
- channelChangeUserNickname(channel, decryptedData, creatorPublicKey);
- }
- catch(sibs::DeserializeException &e)
- {
- fprintf(stderr, "Failed to deserialize nick change\n");
- }
- break;
- }
- case ChannelDataType::CHANGE_AVATAR:
- {
- try
- {
- channelChangeUserAvatar(channel, decryptedData, creatorPublicKey);
- }
- catch(sibs::DeserializeException &e)
- {
- fprintf(stderr, "Failed to deserialize avatar change\n");
- }
- break;
- }
- case ChannelDataType::CHANGE_CHANNEL_NAME:
- {
- try
- {
- channelChangeChannelName(channel, decryptedData, creatorPublicKey);
- }
- catch(sibs::DeserializeException &e)
- {
- fprintf(stderr, "Failed to deserialize channel name change\n");
- }
- break;
- }
- default:
- fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType);
- break;
- }
- }
- else
- {
- fprintf(stderr, "ADD_DATA packet too small, ignoring...\n");
- }
-}
int main(int argc, char **argv)
{
@@ -230,139 +65,151 @@ int main(int argc, char **argv)
//Video video(500, 500, "https://www.youtube.com/watch?v=bs0-EX9mJmg");
- Cache cache;
- Cache::loadBindsFromFile();
-
- Channel offlineChannel("Offline");
- ChannelSidePanel::addChannel(&offlineChannel);
- Channel::setCurrent(&offlineChannel);
+ //Channel offlineChannel("Offline");
+ //ChannelSidePanel::addChannel(&offlineChannel);
+ //Channel::setCurrent(&offlineChannel);
- vector<Channel*> channels;
-
- vector<odhtdb::DatabaseNode> waitingToJoinChannels;
- odhtdb::MapHash<odhtdb::StoredNodeInfo> localNodeUsers;
- string currentUsername;
- string currentPassword;
recursive_mutex channelMessageMutex;
- bool waitingToJoin = false;
- bool loggedIn = false;
sf::Clock lastFocusedTimer;
-
- shared_ptr<odhtdb::Database> database = nullptr;
- odhtdb::DatabaseCallbackFuncs callbackFuncs;
- using LocalUserMessageCallback = function<void(const odhtdb::DatabaseAddNodeRequest &request)>;
- LocalUserMessageCallback onMessageByLocalUser = nullptr;
-
- callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseCreateNodeRequest &request)
+ const char *bootstrapNode = "31.208.244.169";
+
+ std::shared_ptr<Room> offlineRoom = nullptr;
+
+ auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true)
{
- lock_guard<recursive_mutex> lock(channelMessageMutex);
- printf("Create node callback func %s\n", request.nodeHash->toString().c_str());
-
- auto nodeUserData = localNodeUsers.find(*request.nodeHash);
- if(nodeUserData == localNodeUsers.end()) return;
-
- User *localUser;
- if(nodeUserData->second.userKeyPair->getPublicKey() == *request.creatorPublicKey)
- localUser = new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair);
- else
- localUser = new OfflineUser("You");
-
- shared_ptr<odhtdb::Hash> databaseNodeHash = make_shared<odhtdb::Hash>();
- memcpy(databaseNodeHash->getData(), request.nodeHash->getData(), odhtdb::HASH_BYTE_SIZE);
- odhtdb::DatabaseNode databaseNode(nodeUserData->second.nodeEncryptionKey, databaseNodeHash);
- Channel *channel = new Channel("NoChannelName", databaseNode, localUser, database);
- printf("database: %llu\n", database.get());
- ChannelSidePanel::addChannel(channel);
- channels.push_back(channel);
- Channel::setCurrent(channel);
- lastFocusedTimer.restart();
-
- if(localUser->type == User::Type::OFFLINE)
+ auto currentRoom = getCurrentRoom();
+ if(currentRoom && currentRoom->userdata)
{
- User *nodeCreatorUser = new OnlineRemoteUser("NoName", *request.creatorPublicKey);
- channel->addUserLocally(nodeCreatorUser);
- }
-
- for(vector<odhtdb::DatabaseNode>::iterator it = waitingToJoinChannels.begin(); it != waitingToJoinChannels.end(); ++it)
- {
- if(*request.nodeHash == *it->getRequestHash())
- {
- waitingToJoin = true;
- waitingToJoinChannels.erase(it);
- return;
- }
+ auto *roomContainer = static_cast<RoomContainer*>(currentRoom->userdata);
+ roomContainer->messageBoard.addSystemUserMessage(msg, plainText);
}
+ lastFocusedTimer.restart();
};
- callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer, &onMessageByLocalUser](const odhtdb::DatabaseAddNodeRequest &request)
+ RoomCallbackFuncs roomCallbackFuncs;
+ roomCallbackFuncs.connectCallbackFunc = [bootstrapNode, &channelMessageMutex, &offlineRoom, &lastFocusedTimer, &addSystemMessage](std::shared_ptr<Rooms> rooms, const char *errMsg)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
- //printf("Add node callback func %s\n", request.requestHash->toString().c_str());
- for(Channel *channel : channels)
- {
- if(*request.nodeHash == *channel->getNodeInfo().getRequestHash())
+ if(rooms)
+ {
+ std::string msg = "Connected to ";
+ msg += bootstrapNode;
+ msg += ":27130";
+ fprintf(stderr, "%s\n", msg.c_str());
+ setRooms(rooms);
+ auto emptyId = make_shared<odhtdb::Hash>();
+ offlineRoom = make_shared<Room>(rooms.get(), emptyId);
+ offlineRoom->name = "Offline";
+ auto *roomContainer = new RoomContainer(nullptr);
+ roomContainer->offlineRoom = true;
+ offlineRoom->userdata = roomContainer;
+ setCurrentRoom(offlineRoom);
+
+ string commandsMsg = "Available commands: ";
+ for(const auto &commandIt : Command::getCommands())
{
- channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp, request.loadedFromCache);
- if(channel == Channel::getCurrent())
- lastFocusedTimer.restart();
-
- if(!request.loadedFromCache && *request.creatorPublicKey == static_cast<OnlineLocalUser*>(channel->getLocalUser())->getPublicKey())
- {
- if(onMessageByLocalUser)
- onMessageByLocalUser(request);
- }
- return;
+ commandsMsg += "\n/";
+ commandsMsg += commandIt.first;
}
+ addSystemMessage(commandsMsg);
+ }
+ else
+ {
+ std::string errMsgToShow = "Failed to connect to boostrap node, reason: ";
+ errMsgToShow += (errMsg ? errMsg : "unknown");
+ fprintf(stderr, "%s\n", errMsgToShow.c_str());
}
+ lastFocusedTimer.restart();
};
-
- callbackFuncs.addUserCallbackFunc = [&channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseAddUserRequest &request)
+ roomCallbackFuncs.createRoomCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](std::shared_ptr<Room> room)
+ {
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Update side panel
+ //RoomSidePanel::addRoom(room);
+ room->userdata = new RoomContainer(room);
+ lastFocusedTimer.restart();
+ };
+ roomCallbackFuncs.addUserCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomAddUserRequest &request)
+ {
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Update users side panel
+ //chatWindow.addUser(request);
+ request.user->nickname = "Anonymous";
+ lastFocusedTimer.restart();
+ };
+ roomCallbackFuncs.addMessageCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomAddMessageRequest &request)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
- printf("Add user callback. Channel to add user to: %s\n", request.nodeHash->toString().c_str());
- for(Channel *channel : channels)
+ assert(request.room->userdata);
+ auto *roomContainer = static_cast<RoomContainer*>(request.room->userdata);
+ Message *message = new Message(request.message, false);
+ roomContainer->messageBoard.addMessage(message);
+
+ // Show notification if the message is new and window is not focused and the message was sent not sent by us
+ if(!request.loadedFromCache && !windowFocused)
{
- printf("My channel: %s (%s)\n", channel->getNodeInfo().getRequestHash()->toString().c_str(), channel->getName().c_str());
- if(*request.nodeHash == *channel->getNodeInfo().getRequestHash())
+ // TODO: Should this be done?
+ #if 0
+ if(!request.prevMessage || request.message->timestampSeconds >= request.prevMessage->timestampSeconds)
{
- printf("Add user to one of my channels\n");
- auto nodeUserData = localNodeUsers.find(*request.nodeHash);
-
- User *userToAdd = channel->getUserByPublicKey(*request.userToAddPublicKey);
- if(userToAdd)
- {
- fprintf(stderr, "User %s already exists in channel\n", request.userToAddPublicKey->toString().c_str());
- return;
- }
-
- if(channel == Channel::getCurrent())
- lastFocusedTimer.restart();
-
- if(*request.userToAddPublicKey == nodeUserData->second.userKeyPair->getPublicKey())
- {
- printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorPublicKey->toString().c_str());
- channel->replaceLocalUser(new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair));
- waitingToJoin = false;
- return;
- }
-
- User *newRemoteUser = new OnlineRemoteUser("NoName", *request.userToAddPublicKey);
- channel->addUserLocally(newRemoteUser);
- return;
+
+ }
+ #endif
+ if(request.room->localUser && request.room->localUser != request.message->creator)
+ {
+ string cmd = "notify-send dchat '";
+ cmd += escapeCommand(request.message->text);
+ cmd += "'";
+ system(cmd.c_str());
}
}
+
+ lastFocusedTimer.restart();
};
- database = odhtdb::Database::connect("127.0.0.1", 27130, Cache::getDchatDir(), callbackFuncs).get();
-
- auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true)
+ roomCallbackFuncs.userChangeNicknameCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const UserChangeNicknameRequest &request)
+ {
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Update panels
+ //chatWindow.setUserNickname(request);
+ lastFocusedTimer.restart();
+ };
+ roomCallbackFuncs.userChangeAvatarCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const UserChangeAvatarRequest &request)
{
- Channel::getCurrent()->addSystemMessage(msg, plainText);
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Update panels
+ //chatWindow.setUserAvatar(request);
lastFocusedTimer.restart();
};
+ roomCallbackFuncs.changeRoomNameCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomChangeNameRequest &request)
+ {
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Update panels
+ //chatWindow.changeRoomName(request);
+ lastFocusedTimer.restart();
+ };
+ roomCallbackFuncs.receiveInviteUserCallbackFunc = [&channelMessageMutex](const InviteUserRequest &request)
+ {
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ // TODO: Add invite to a list which we can accept the user from
+ //chatWindow.addInviteRequest(request);
+ // For now automatically add user to the room
+ fprintf(stderr, "User invite request message: %s\n", request.message.c_str());
+ assert(!request.room->localUser->groups.empty());
+ // TODO: Add user to guest group. Right now we are adding the user to our group, which in this case is the admin group...
+ try
+ {
+ request.room->addUser(request.userPublicKey, request.room->localUser->groups[0]);
+ fprintf(stderr, "Successfully added user %s to our group\n", request.userPublicKey.toString().c_str());
+ }
+ catch(odhtdb::PermissionDeniedException &e)
+ {
+ fprintf(stderr, "Failed to add user %s to our group, reason: %s\n", request.userPublicKey.toString().c_str(), e.what());
+ }
+ };
// Login to account
- Command::add("login", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, &currentUsername, &currentPassword](const vector<string> &args)
+ Command::add("login", [&channelMessageMutex](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 2)
@@ -370,37 +217,24 @@ int main(int argc, char **argv)
fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size());
return;
}
+
+ auto rooms = getRooms();
+ if(!rooms)
+ return;
try
{
- localNodeUsers = database->getStoredNodeUserInfoDecrypted(args[0], args[1]);
-
- ChannelSidePanel::removeAllChannels();
- for(Channel *channel : channels)
- {
- delete channel;
- }
- channels.clear();
-
- printf("Loading %u channel(s) for user\n", localNodeUsers.size());
- for(auto &localNodeUser : localNodeUsers)
- {
- database->loadNode(localNodeUser.first);
- }
-
+ rooms->loginUser(args[0], args[1]);
printf("Successfully logged into user %s\n", args[0].c_str());
- currentUsername = args[0];
- currentPassword = args[1];
- loggedIn = true;
}
catch(std::exception &e)
{
- fprintf(stderr, "Failed to login, reason: %s\n", e.what());
+ fprintf(stderr, "Failed to login to user %s, reason: %s\n", args[0].c_str(), e.what());
}
});
// Register account
- Command::add("register", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, &currentUsername, &currentPassword](const vector<string> &args)
+ Command::add("register", [&channelMessageMutex](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 2)
@@ -408,36 +242,26 @@ int main(int argc, char **argv)
fprintf(stderr, "Expected 2 arguments for command register (username and password), got %u argument(s)\n", args.size());
return;
}
+
+ auto rooms = getRooms();
+ if(!rooms)
+ return;
try
{
- database->storeUserWithoutNodes(args[0], args[1]);
+ rooms->registerUser(args[0], args[1]);
+ printf("Successfully registered user %s\n", args[0].c_str());
}
catch(odhtdb::SqlExecException &e)
{
- fprintf(stderr, "User with name %s already exists in storage\n", args[0].c_str());
+ fprintf(stderr, "Failed to register user %s, reason: %s", args[0].c_str(), e.what());
return;
}
-
- printf("Registered user %s\n", args[0].c_str());
-
- ChannelSidePanel::removeAllChannels();
- for(Channel *channel : channels)
- {
- delete channel;
- }
- channels.clear();
- localNodeUsers.clear();
-
- printf("Successfully logged into user %s\n", args[0].c_str());
- currentUsername = args[0];
- currentPassword = args[1];
- loggedIn = true;
});
// TODO: Use database->addData to change channel name
// Create channel
- Command::add("cc", [&database, &channels, &channelMessageMutex, &loggedIn, &localNodeUsers, &currentUsername, &currentPassword, &lastFocusedTimer, addSystemMessage](const vector<string> &args)
+ Command::add("cc", [&channelMessageMutex, &lastFocusedTimer](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 1)
@@ -445,218 +269,83 @@ int main(int argc, char **argv)
fprintf(stderr, "Expected 1 argument for command cc (channel name), got %u argument(s)\n", args.size());
return;
}
+
+ auto rooms = getRooms();
+ if(!rooms)
+ return;
- if(!loggedIn)
+ if(!rooms->isLoggedIn())
{
- fprintf(stderr, "You are not logged in. Please login before creating a channel\n");
+ fprintf(stderr, "You are not logged in. Please login before creating a room\n");
return;
}
- auto createResponse = database->create();
- printf("Created channel %s\n", createResponse->getRequestHash()->toString().c_str());
-
- User *newLocalUser = new OnlineLocalUser("NoName", *createResponse->getNodeAdminKeyPair());
- odhtdb::DatabaseNode databaseNode(createResponse->getNodeEncryptionKey(), createResponse->getRequestHash());
- Channel *channel = new Channel(args[0], databaseNode, newLocalUser, database);
- ChannelSidePanel::addChannel(channel);
- channels.push_back(channel);
- Channel::setCurrent(channel);
- channel->setName(args[0]);
- lastFocusedTimer.restart();
-
- localNodeUsers[*createResponse->getRequestHash()] = { createResponse->getNodeEncryptionKey(), createResponse->getNodeAdminKeyPair() };
- database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *createResponse->getNodeAdminKeyPair());
- addSystemMessage("Channel created and stored in database");
+ try
+ {
+ rooms->createRoom(args[0]);
+ printf("Successfully created room %s\n", args[0].c_str());
+ lastFocusedTimer.restart();
+ }
+ catch(std::exception &e)
+ {
+ fprintf(stderr, "Failed to create room %s, reason: %s\n", args[0].c_str(), e.what());
+ }
});
- // Create invite key
- Command::add("invite", [&channelMessageMutex, &offlineChannel, &database, addSystemMessage](const vector<string> &args)
+ // Get invite key
+ Command::add("invitekey", [&channelMessageMutex, &addSystemMessage](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 0)
{
- fprintf(stderr, "Expected 0 arguments for command invite, got %u argument(s)\n", args.size());
+ fprintf(stderr, "Expected 0 arguments for command invitekey, got %u argument(s)\n", args.size());
return;
}
- Channel *currentChannel = Channel::getCurrent();
- if(currentChannel == &offlineChannel)
+ auto currentRoom = getCurrentRoom();
+ if(!currentRoom || static_cast<RoomContainer*>(currentRoom->userdata)->offlineRoom || currentRoom->inviteKey.empty())
{
- addSystemMessage("You need to be in a channel to create an invite key");
+ fprintf(stderr, "You need to be in a room to get the invite key\n");
return;
}
-
- // TODO: Verify user has permission to add users before generating invite key, otherwise invite will fail and the users attempting to join will wait in futile
-
- auto channelNodeHash = currentChannel->getNodeInfo().getRequestHash();
- auto channelEncryptionKey = currentChannel->getNodeInfo().getNodeEncryptionKey();
- shared_ptr<odhtdb::OwnedByteArray> encryptionKey = make_shared<odhtdb::OwnedByteArray>(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
- odhtdb::Encryption::generateKey((unsigned char*)encryptionKey->data);
-
- string inviteKey = odhtdb::bin2hex((const char*)channelNodeHash->getData(), channelNodeHash->getSize());
- inviteKey += '&';
- inviteKey += odhtdb::bin2hex((const char*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
-
- string msg = "You are now listening for users to join the channel using the key: ";
- msg += inviteKey;
-
+
+ std::string msg = "Invite key: ";
+ msg += currentRoom->inviteKey;
addSystemMessage(msg);
- printf("%s\n", msg.c_str());
-
- sibs::SafeSerializer keySerializer;
- keySerializer.add((const u8*)channelNodeHash->getData(), channelNodeHash->getSize());
- keySerializer.add((const u8*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
- odhtdb::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size());
- database->receiveCustomMessage(key, [&channelMessageMutex, encryptionKey, channelEncryptionKey, currentChannel, database](const void *data, usize size)
- {
- // TODO: User can remove channel @currentChannel before we get here, meaning @currentChannel is deleted and would be invalid; causing the program to crash
- try
- {
- sibs::SafeDeserializer deserializer((const u8*)data, size);
- u8 userToAddPublicKeyRaw[odhtdb::PUBLIC_KEY_NUM_BYTES];
- deserializer.extract(userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES);
- odhtdb::Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES);
- string unsignedEncryptedMsg = userToAddPublicKey.unsign(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize()));
-
- sibs::SafeDeserializer encryptedDataDeserializer((const u8*)unsignedEncryptedMsg.data(), unsignedEncryptedMsg.size());
- u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE];
- encryptedDataDeserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE);
- odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)encryptedDataDeserializer.getBuffer(), encryptedDataDeserializer.getSize()),
- odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE),
- encryptionKey->getView());
-
- // TODO: Create GUI for accepting users into channel instead of accepting ALL users
- sibs::SafeSerializer encryptedDataSerializer;
- encryptedDataSerializer.add((const u8*)channelEncryptionKey->data, channelEncryptionKey->size);
- encryptedDataSerializer.add((const u8*)userToAddPublicKey.getData(), userToAddPublicKey.getSize());
- odhtdb::Encryption encryptedChannelKey(odhtdb::DataView((void*)encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size()), encryptionKey->getView());
-
- sibs::SafeSerializer serializer;
- serializer.add((const u8*)encryptedChannelKey.getNonce().data, encryptedChannelKey.getNonce().size);
- serializer.add((const u8*)encryptedChannelKey.getCipherText().data, encryptedChannelKey.getCipherText().size);
- const auto &localUserPublicKey = static_cast<OnlineLocalUser*>(currentChannel->getLocalUser())->getPublicKey();
- auto localUserGroups = database->getUserGroups(*currentChannel->getNodeInfo().getRequestHash(), localUserPublicKey);
- if(localUserGroups.empty())
- {
- fprintf(stderr, "No group to add user to...\n");
- return sibs::SafeSerializer();
- }
- lock_guard<recursive_mutex> lock(channelMessageMutex);
- currentChannel->addUser(userToAddPublicKey, localUserGroups[0].getView());
- return serializer;
- }
- catch(std::exception &e)
- {
- fprintf(stderr, "Failed while parsing user data to add to channel, reason: %s\n", e.what());
- }
- return sibs::SafeSerializer();
- });
+ fprintf(stderr, "%s\n", msg.c_str());
});
// Join channel using invite key
- Command::add("jc", [&loggedIn, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels, &currentUsername, &currentPassword](const vector<string> &args)
+ Command::add("jc", [&channelMessageMutex](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
- if(args.size() != 1)
+ if(args.size() != 2)
{
- fprintf(stderr, "Expected 1 argument for command jc (channel join key), got %u argument(s)\n", args.size());
+ fprintf(stderr, "Expected 2 arguments for command jc (channel join key and message), got %u argument(s)\n", args.size());
return;
}
- if(args[0].size() != 129)
- {
- fprintf(stderr, "Expected join key to be 129 characters, was %u character(s)\n", args[0].size());
+ auto rooms = getRooms();
+ if(!rooms)
return;
- }
- if(!loggedIn)
+ if(!rooms->isLoggedIn())
{
- fprintf(stderr, "You are not logged in. Please login before joining a channel\n");
+ fprintf(stderr, "You are not logged in. Please login before joining a room\n");
return;
}
-
- string nodeHashBinRaw = odhtdb::hex2bin(args[0].c_str(), 64);
- shared_ptr<odhtdb::Hash> nodeHash = make_shared<odhtdb::Hash>();
- memcpy(nodeHash->getData(), nodeHashBinRaw.data(), nodeHashBinRaw.size());
- auto nodeUserIt = localNodeUsers.find(*nodeHash);
- if(nodeUserIt != localNodeUsers.end())
+
+ try
{
- fprintf(stderr, "You have already joined the channel %s\n", args[0].c_str());
- return;
+ if(rooms->requestJoinRoom(args[0], args[1]))
+ fprintf(stderr, "No error while joining room. Waiting to be added...");
}
-
- string encryptionKeyBinRaw = odhtdb::hex2bin(args[0].c_str() + 65, 64);
- shared_ptr<odhtdb::OwnedByteArray> encryptionKey = make_shared<odhtdb::OwnedByteArray>(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
- memcpy(encryptionKey->data, encryptionKeyBinRaw.data(), encryptionKeyBinRaw.size());
-
- shared_ptr<odhtdb::Signature::KeyPair> keyPair = make_shared<odhtdb::Signature::KeyPair>();
- sibs::SafeSerializer serializer;
- serializer.add((const u8*)keyPair->getPublicKey().getData(), keyPair->getPublicKey().getSize());
-
- const char *msg = "please let me join";
- odhtdb::Encryption encryptedJoinMsg(odhtdb::DataView((void*)msg, strlen(msg)), encryptionKey->getView());
- sibs::SafeSerializer encryptedDataSerializer;
- encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getNonce().data, encryptedJoinMsg.getNonce().size);
- encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getCipherText().data, encryptedJoinMsg.getCipherText().size);
- string signedEncryptedMsg = keyPair->getPrivateKey().sign(odhtdb::DataView(encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size()));
- serializer.add((const u8*)signedEncryptedMsg.data(), signedEncryptedMsg.size());
-
- sibs::SafeSerializer keySerializer;
- keySerializer.add((const u8*)nodeHash->getData(), nodeHash->getSize());
- keySerializer.add((const u8*)encryptionKeyBinRaw.data(), odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
- odhtdb::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size());
- database->sendCustomMessage(key, serializer.getBuffer().data(), serializer.getBuffer().size(),
- [&database, nodeHash, encryptionKey, &waitingToJoinChannels, &channelMessageMutex, keyPair, &currentUsername, &currentPassword, &localNodeUsers]
- (bool gotResponse, const void *data, usize size)
+ catch(std::exception &e)
{
- if(!gotResponse)
- {
- printf("We didn't get a response from anybody in the channel. Is there nobody that can add us (nobody with the permission required to do so or no online users) or is the node hash invalid?\n");
- return false;
- }
-
- try
- {
- sibs::SafeDeserializer deserializer((const u8*)data, size);
- u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE];
- deserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE);
- odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize()),
- odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE),
- encryptionKey->getView());
- if(decryptedMsg.getDecryptedText().size != odhtdb::ENCRYPTION_KEY_BYTE_SIZE + odhtdb::PUBLIC_KEY_NUM_BYTES)
- {
- fprintf(stderr, "Invite response was of unexpected size, maybe it wasn't meant for us?\n");
- return true;
- }
-
- odhtdb::DataView channelEncryptionKeyRaw(decryptedMsg.getDecryptedText().data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
- odhtdb::DataView invitedUserPublicKey((u8*)decryptedMsg.getDecryptedText().data + odhtdb::ENCRYPTION_KEY_BYTE_SIZE, odhtdb::PUBLIC_KEY_NUM_BYTES);
- if(memcmp(keyPair->getPublicKey().getData(), invitedUserPublicKey.data, odhtdb::PUBLIC_KEY_NUM_BYTES) != 0)
- {
- fprintf(stderr, "Invite response was not meant for us\n");
- return true;
- }
-
- shared_ptr<odhtdb::OwnedByteArray> channelEncryptionKey = make_shared<odhtdb::OwnedByteArray>(new u8[channelEncryptionKeyRaw.size], channelEncryptionKeyRaw.size);
- memcpy(channelEncryptionKey->data, channelEncryptionKeyRaw.data, channelEncryptionKeyRaw.size);
- odhtdb::DatabaseNode databaseNode(channelEncryptionKey, nodeHash);
- localNodeUsers[*nodeHash] = { channelEncryptionKey, keyPair };
- database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *keyPair);
- printf("Got a response from a person in the channel, we might get added...\n");
- waitingToJoinChannels.push_back(databaseNode);
- lock_guard<recursive_mutex> lock(channelMessageMutex);
- database->seed(databaseNode, odhtdb::DatabaseFetchOrder::NEWEST_FIRST);
- return false;
- }
- catch(std::exception &e)
- {
- fprintf(stderr, "Failed while parsing join response for invite link, reason: %s\n", e.what());
- }
- return true;
- });
- waitingToJoin = true;
+ fprintf(stderr, "Failed to join room %s, reason: %s", args[0].c_str(), e.what());
+ }
});
-
+
// Scale UI
Command::add("scale", [](const vector<string> &args)
{
@@ -752,8 +441,8 @@ int main(int argc, char **argv)
addSystemMessage(msg, false);
});
- // Change nick of current user in current channel
- Command::add("nick", [&loggedIn, &offlineChannel, addSystemMessage](const vector<string> &args)
+ // Change nick of current user in current room
+ Command::add("nick", [addSystemMessage](const vector<string> &args)
{
if(args.size() != 1)
{
@@ -763,16 +452,11 @@ int main(int argc, char **argv)
addSystemMessage(errMsg);
return;
}
-
- if(!loggedIn)
- {
- addSystemMessage("You need to be logged in to change your nickname");
- return;
- }
-
- if(Channel::getCurrent() == &offlineChannel)
+
+ auto currentRoom = getCurrentRoom();
+ if(!currentRoom || static_cast<RoomContainer*>(currentRoom->userdata)->offlineRoom)
{
- addSystemMessage("You need to be in a channel to change your nickname");
+ fprintf(stderr, "You need to be in a room to change your name\n");
return;
}
@@ -782,14 +466,14 @@ int main(int argc, char **argv)
return;
}
- Channel::getCurrent()->changeNick(args[0]);
+ currentRoom->setUserNickname(args[0]);
string msg = "Your nickname was changed to ";
msg += args[0];
addSystemMessage(msg);
});
// Change avatar of current user in current channel
- Command::add("avatar", [&loggedIn, &offlineChannel, addSystemMessage](const vector<string> &args)
+ Command::add("avatar", [addSystemMessage](const vector<string> &args)
{
if(args.size() != 1)
{
@@ -800,15 +484,10 @@ int main(int argc, char **argv)
return;
}
- if(!loggedIn)
+ auto currentRoom = getCurrentRoom();
+ if(!currentRoom || static_cast<RoomContainer*>(currentRoom->userdata)->offlineRoom)
{
- addSystemMessage("You need to be logged in to change your avatar");
- return;
- }
-
- if(Channel::getCurrent() == &offlineChannel)
- {
- addSystemMessage("You need to be in a channel to change your avatar");
+ fprintf(stderr, "You need to be in a room to change your avatar\n");
return;
}
@@ -820,37 +499,31 @@ int main(int argc, char **argv)
if(looksLikeUrl(args[0]))
{
- Channel::getCurrent()->setAvatar(args[0]);
+ currentRoom->setAvatarUrl(args[0]);
addSystemMessage("Your avatar has been changed (Note: max avatar size is 1 Mb, if your avatar is larger then it will not be visible)");
}
else
{
- addSystemMessage("Avatar url needs to start with either http:// or https:// and include a dot");
+ addSystemMessage("Avatar url needs to start with either http://, https:// or www.");
}
});
- // Change name of the current channel
- Command::add("channelname", [&offlineChannel, addSystemMessage](const vector<string> &args)
+ // Change name of the current room
+ Command::add("roomname", [addSystemMessage](const vector<string> &args)
{
if(args.size() != 1)
{
- string errMsg = "Expected 1 argument for command channelname, got ";
+ string errMsg = "Expected 1 argument for command roomname, got ";
errMsg += to_string(args.size());
errMsg += " argument(s)";
addSystemMessage(errMsg);
return;
}
- Channel *currentChannel = Channel::getCurrent();
- if(currentChannel == &offlineChannel)
- {
- addSystemMessage("You need to be in a channel to change channel name");
- return;
- }
-
- if(!currentChannel->getLocalUser()->isOnlineUser())
+ auto currentRoom = getCurrentRoom();
+ if(!currentRoom || static_cast<RoomContainer*>(currentRoom->userdata)->offlineRoom)
{
- addSystemMessage("You need to be logged in to change channel name");
+ fprintf(stderr, "You can't change room name size you are not in a room\n");
return;
}
@@ -859,297 +532,26 @@ int main(int argc, char **argv)
addSystemMessage("Channel name has to be between 1 and 32 bytes long");
return;
}
-
- int localUserPermissionLevel = currentChannel->getUserLowestPermissionLevel(static_cast<OnlineLocalUser*>(currentChannel->getLocalUser()));
- if(localUserPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN)
- {
- addSystemMessage("You need to be admin to change channel name");
- return;
- }
- currentChannel->setName(args[0]);
+ currentRoom->setName(args[0]);
string msg = "Channel name has been changed to ";
msg += args[0];
addSystemMessage(msg);
});
- Command::add("clearcache", [database](const vector<string> &args)
- {
- printf("Cleared cache (%d bytes)\n", database->clearCache());
- });
+ //Command::add("clearcache", [](const vector<string> &args)
+ //{
+ // printf("Cleared cache (%d bytes)\n", database->clearCache());
+ //});
- string commandsMsg = "Available commands: ";
- for(const auto &commandIt : Command::getCommands())
- {
- commandsMsg += "\n/";
- commandsMsg += commandIt.first;
- }
- addSystemMessage(commandsMsg);
-
- odhtdb::MapHash<u64> myDiscordIdsByChannel;
-
bool running = true;
- thread rpcThread([&running, &onMessageByLocalUser, &channels, &lastFocusedTimer, &myDiscordIdsByChannel]()
- {
- Rpc rpc(5555);
- mutex messageMutex;
- vector<msgpack::sbuffer> messagesToSend;
- onMessageByLocalUser = [&messagesToSend, &messageMutex](const odhtdb::DatabaseAddNodeRequest &request)
- {
- lock_guard<mutex> lock(messageMutex);
- auto channelDataType = (ChannelDataType)static_cast<const char*>(request.decryptedData.data)[0];
- usize size = request.decryptedData.size - 1;
- const char *data = (const char*)request.decryptedData.data + 1;
- const char *action = nullptr;
- if(channelDataType == ChannelDataType::ADD_MESSAGE)
- action = "addMessage";
-
- if(!action) return;
- vector<string> msg = { action, string(data, data + size), request.nodeHash->toString() };
- msgpack::sbuffer buffer;
- msgpack::pack(buffer, msg);
- messagesToSend.emplace_back(move(buffer));
- };
-
- while(running)
- {
- rpc.recv([&channels, &lastFocusedTimer, &myDiscordIdsByChannel](zmq::message_t *message)
- {
- try
- {
- msgpack::object_handle oh = msgpack::unpack((const char*)message->data(), message->size());
- auto deserialized = oh.get();
- vector<string> msg;
- deserialized.convert(msg);
- if(msg.size() < 2)
- {
- fprintf(stderr, "Rpc receive, data length expected to be at least 2, was %u\n", msg.size());
- return;
- }
- auto &action = msg[0];
-
- string dchatChannelIdRaw = odhtdb::hex2bin(msg[1].c_str(), msg[1].size());
- odhtdb::Hash dchatChannelId;
- memcpy(dchatChannelId.getData(), dchatChannelIdRaw.data(), dchatChannelIdRaw.size());
- Channel *bridgedChannel = nullptr;
- for(Channel *channel : channels)
- {
- if(*channel->getNodeInfo().getRequestHash() == dchatChannelId)
- {
- bridgedChannel = channel;
- break;
- }
- }
-
- if(!bridgedChannel)
- {
- fprintf(stderr, "Rcp addMessage, invalid dchat channel %s\n", msg[1].c_str());
- return;
- }
-
- if(bridgedChannel == Channel::getCurrent())
- lastFocusedTimer.restart();
+ Cache *cache = ResourceCache::getCache();
+ Rooms::connect(bootstrapNode, 27130, roomCallbackFuncs);
+ Chatbar::loadBindsFromFile();
- fprintf(stderr, "Received rpc, action: %s\n", action.c_str());
- if(action == "addMessage")
- {
- if((msg.size() - 2) % 4 != 0)
- {
- fprintf(stderr, "Rpc addMessage, request was malformed\n");
- return;
- }
-
- for(size_t i = 2; i < msg.size(); i += 4)
- {
- auto &content = msg[i];
- auto &discordUserId = msg[i + 1];
- u64 discordUserIdNumber = 0;
- auto &discordUserName = msg[i + 2];
- auto &messageTimestampMillisec = msg[i + 3];
- u64 messageTimestampSecondsNumber = 0;
-
- try
- {
- discordUserIdNumber = stoull(discordUserId);
- }
- catch(...)
- {
- fprintf(stderr, "Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
- return;
- }
-
- try
- {
- messageTimestampSecondsNumber = stoull(messageTimestampMillisec) / 1000;
- }
- catch(...)
- {
- fprintf(stderr, "Rpc receive, failed to convert discord message timestamp to uint64_t: %s\n", messageTimestampMillisec.c_str());
- return;
- }
-
- auto myDiscordIdIt = myDiscordIdsByChannel.find(dchatChannelId);
- if(myDiscordIdIt != myDiscordIdsByChannel.end() && myDiscordIdIt->second == discordUserIdNumber)
- {
- auto &channelMessages = bridgedChannel->getMessageBoard().getMessages();
- Message *myLatestMessage = nullptr;
- for(auto it = channelMessages.rbegin(), end = channelMessages.rend(); it != end; ++it)
- {
- if((*it)->user == bridgedChannel->getLocalUser())
- {
- myLatestMessage = *it;
- break;
- }
- }
-
- if(myLatestMessage && (i64)messageTimestampSecondsNumber - (i64)myLatestMessage->timestampSeconds <= 3)
- {
- return;
- /*
- auto myMessageUtf8 = myLatestMessage->text.getString().toUtf8();
- odhtdb::Hash myMessageHash(myMessageUtf8.data(), myMessageUtf8.size());
- odhtdb::Hash myDiscordMessageHash(content.data(), content.size());
- if(myMessageHash == myDiscordMessageHash)
- return;
- */
- }
- }
-
- bridgedChannel->addLocalDiscordMessage(discordUserName, discordUserIdNumber, content, bridgedChannel->getLocalUser(), messageTimestampSecondsNumber, odhtdb::Hash(messageTimestampMillisec.c_str(), messageTimestampMillisec.size()));
- }
- }
- else if(action == "addUser")
- {
- if((msg.size() - 2) % 4 != 0)
- {
- fprintf(stderr, "Rpc addUser, request was malformed\n");
- return;
- }
-
- for(size_t i = 2; i < msg.size(); i += 4)
- {
- auto &discordUsername = msg[i];
- auto &discordUserId = msg[i + 1];
- auto &userStatus = msg[i + 2];
- auto &avatarURL = msg[i + 3];
-
- try
- {
- u64 discordUserIdNumber = stoull(discordUserId);
- bool online = (userStatus != "offline");
- printf("Rpc, adding user %s with status %s\n", discordUsername.c_str(), userStatus.c_str());
- DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber);
- if(discordUser)
- {
- discordUser->connected = online;
- }
- else
- {
- discordUser = new DiscordServiceUser(discordUsername, discordUserIdNumber, online);
- bridgedChannel->getDiscordService()->addUser(discordUser);
- }
- discordUser->avatarUrl = avatarURL;
- }
- catch(...)
- {
- fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
- // Ignore for now.. should we really handle this error other than showing warning?
- }
- }
- }
- else if(action == "removeUser")
- {
- for(size_t i = 2; i < msg.size(); ++i)
- {
- auto &discordUserId = msg[i];
- try
- {
- u64 discordUserIdNumber = stoull(discordUserId);
- bridgedChannel->getDiscordService()->removeUser(discordUserIdNumber);
- }
- catch(...)
- {
- fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
- // Ignore for now.. should we really handle this error other than showing warning?
- }
- }
- }
- else if(action == "statusChange")
- {
- if((msg.size() - 2) != 2)
- {
- fprintf(stderr, "Rpc statusChange, request was malformed\n");
- return;
- }
-
- auto &discordUserId = msg[2];
- auto &userStatus = msg[3];
-
- try
- {
- u64 discordUserIdNumber = stoull(discordUserId);
- DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber);
- if(discordUser)
- {
- discordUser->connected = (userStatus != "offline");
- printf("Rcp statusChange, changed user %s (%s) status to %s\n", discordUserId.c_str(), discordUser->getName().c_str(), userStatus.c_str());
- }
- }
- catch(...)
- {
- fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str());
- // Ignore for now.. should we really handle this error other than showing warning?
- }
- }
- else if(action == "addMe")
- {
- if((msg.size() - 2) != 1)
- {
- fprintf(stderr, "Rpc addMe, request was malformed\n");
- return;
- }
-
- auto &myDiscordId = msg[2];
- try
- {
- u64 myDiscordIdNumber = stoull(myDiscordId);
- myDiscordIdsByChannel[dchatChannelId] = myDiscordIdNumber;
- }
- catch(...)
- {
- fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", myDiscordId.c_str());
- // Ignore for now.. should we really handle this error other than showing warning?
- }
- }
- else
- {
- fprintf(stderr, "Rcp received unknown action %s\n", action.c_str());
- }
- }
- catch(msgpack::type_error &e)
- {
- fprintf(stderr, "Failed to deserialize received rpc, error: %s\n", e.what());
- }
- });
- {
- lock_guard<mutex> lock(messageMutex);
- for(auto &messageToSend : messagesToSend)
- {
- fprintf(stderr, "Rpc, sending message\n");
- rpc.send(messageToSend.data(), messageToSend.size());
- }
- messagesToSend.clear();
- }
- this_thread::sleep_for(chrono::milliseconds(50));
- }
- });
-
- sf::Clock frameTimer;
-
while (running)
{
- frameTimer.restart();
- Channel *currentChannel = Channel::getCurrent();
+ std::shared_ptr<Room> currentRoom = getCurrentRoom();
sf::Event event;
while (window.pollEvent(event))
@@ -1195,17 +597,21 @@ int main(int argc, char **argv)
if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250)
{
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
GlobalContextMenu::processEvent(event);
- currentChannel->processEvent(event, cache);
+ if(currentRoom && currentRoom->userdata) {
+ auto *roomContainer = static_cast<RoomContainer*>(currentRoom->userdata);
+ roomContainer->processEvent(event, cache);
+ }
}
lastFocusedTimer.restart();
}
- for(Channel *channel : channels)
- {
- channel->update();
- }
+ //for(Channel *channel : channels)
+ // {
+ // channel->update();
+ // }
if((!windowFocused || !focused) && lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000)
{
@@ -1214,43 +620,26 @@ int main(int argc, char **argv)
}
window.clear(ColorScheme::getBackgroundColor());
- ChannelSidePanel::draw(window);
- currentChannel->draw(window, cache);
- UsersSidePanel::draw(window, cache);
- ChannelTopPanel::draw(window);
- GlobalContextMenu::draw(window);
- ImagePreview::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/Nunito-Regular.ttf");
- const float FONT_SIZE = (double)windowSize.x / 40.0;
- 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);
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
+ RoomSidePanel::draw(window);
+ if(currentRoom && currentRoom->userdata) {
+ auto *roomContainer = static_cast<RoomContainer*>(currentRoom->userdata);
+ roomContainer->draw(window, cache);
+ }
+ UsersSidePanel::draw(window, cache);
+ RoomTopPanel::draw(window);
+ GlobalContextMenu::draw(window);
+ ImagePreview::draw(window);
}
//video.draw(window);
window.display();
}
-
- onMessageByLocalUser = nullptr;
- rpcThread.join();
-
- for(Channel *channel : channels)
- {
- delete channel;
- }
// We need to wait until our `ping` packet for disconnecting is sent to all channels
- printf("Shutting down...\n");
- this_thread::sleep_for(chrono::seconds(3));
+ //printf("Shutting down...\n");
+ //this_thread::sleep_for(chrono::seconds(3));
return 0;
}