diff options
author | dec05eba <dec05eba@protonmail.com> | 2019-04-08 21:04:12 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2019-04-08 21:04:17 +0200 |
commit | 725ea566a2b6a12e0a02e4f570b6e99102e2d21b (patch) | |
tree | d35a338392e15f50402c2055d520e7b1c3ea36a2 /src | |
parent | 4aac8df198e3a5bd9c6efc95cdf4c520c2e05401 (diff) |
Refactor, remove a lot of code and use dchat core instead
Diffstat (limited to 'src')
-rw-r--r-- | src/Cache.cpp | 427 | ||||
-rw-r--r-- | src/Channel.cpp | 405 | ||||
-rw-r--r-- | src/Chatbar.cpp | 50 | ||||
-rw-r--r-- | src/Gif.cpp | 219 | ||||
-rw-r--r-- | src/ImagePreview.cpp | 81 | ||||
-rw-r--r-- | src/Message.cpp | 17 | ||||
-rw-r--r-- | src/MessageBoard.cpp | 180 | ||||
-rw-r--r-- | src/ResourceCache.cpp | 81 | ||||
-rw-r--r-- | src/Room.cpp | 27 | ||||
-rw-r--r-- | src/RoomContainer.cpp | 24 | ||||
-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.cpp | 39 | ||||
-rw-r--r-- | src/StaticImage.cpp | 10 | ||||
-rw-r--r-- | src/Suggestions.cpp | 2 | ||||
-rw-r--r-- | src/Text.cpp | 191 | ||||
-rw-r--r-- | src/User.cpp | 93 | ||||
-rw-r--r-- | src/UsersSidePanel.cpp | 92 | ||||
-rw-r--r-- | src/WebPagePreview.cpp | 9 | ||||
-rw-r--r-- | src/main.cpp | 1075 |
20 files changed, 723 insertions, 2367 deletions
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, ¤tUsername, ¤tPassword](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, ¤tUsername, ¤tPassword](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, ¤tUsername, ¤tPassword, &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, ¤tUsername, ¤tPassword](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, ¤tUsername, ¤tPassword, &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; } |