aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-04-21 03:46:58 +0200
committerdec05eba <dec05eba@protonmail.com>2018-04-21 03:50:59 +0200
commit09a8ade6becca2a71f45ff0db5f4bf6d64afb212 (patch)
tree3cc733a5af1323c57f7dc4c18747ae0c7de78be6 /src
parentde059e317e43fa1b94d77fd981be68b86bf6de6e (diff)
Add support for static image emoji
Emoji are downloaded asynchronously using remote program (curl). Need to add support for converting [inline](url) chat message emoji and gifs.
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp158
-rw-r--r--src/Channel.cpp79
-rw-r--r--src/ChannelSidePanel.cpp9
-rw-r--r--src/Message.cpp5
-rw-r--r--src/MessageBoard.cpp104
-rw-r--r--src/MessagePart.cpp24
-rw-r--r--src/ResourceCache.cpp25
-rw-r--r--src/main.cpp71
8 files changed, 376 insertions, 99 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp
new file mode 100644
index 0000000..7e3272a
--- /dev/null
+++ b/src/Cache.cpp
@@ -0,0 +1,158 @@
+#include "../include/Cache.hpp"
+#include "../include/env.hpp"
+#include "../include/ResourceCache.hpp"
+#include <boost/filesystem/convenience.hpp>
+#include <unordered_map>
+#include <process.hpp>
+#include <odhtdb/Hash.hpp>
+
+#if OS_FAMILY == OS_FAMILY_POSIX
+#include <pwd.h>
+#else
+#include <string>
+#endif
+
+using namespace std;
+using namespace TinyProcessLib;
+
+namespace dchat
+{
+ unordered_map<string, ImageByUrlResult> imageUrlCache;
+
+ 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() / ".dchat";
+ boost::filesystem::create_directories(dchatHomeDir);
+ return dchatHomeDir;
+ }
+
+ Cache::Cache()
+ {
+ downloadWaitThread = thread([this]
+ {
+ while(true)
+ {
+ imageDownloadMutex.lock();
+ for(vector<ImageDownloadInfo>::iterator it = imageDownloadProcesses.begin(); it != imageDownloadProcesses.end();)
+ {
+ int exitStatus;
+ if(it->process->try_get_exit_status(exitStatus))
+ {
+ bool failed = exitStatus != 0;
+
+ if(!failed)
+ {
+ boost::filesystem::path filepath = getDchatDir();
+ odhtdb::Hash urlHash(it->url.data(), it->url.size());
+ filepath /= urlHash.toString();
+
+ try
+ {
+ const sf::Texture *texture = ResourceCache::getTexture(filepath.string());
+ ImageByUrlResult &imageByUrlResult = imageUrlCache[it->url];
+ imageByUrlResult.texture = texture;
+ imageByUrlResult.type = ImageByUrlResult::Type::CACHED;
+ }
+ catch(FailedToLoadResourceException &e)
+ {
+ fprintf(stderr, "%s\n", e.what());
+ failed = true;
+ }
+ }
+
+ if(failed)
+ {
+ imageUrlCache[it->url].type = ImageByUrlResult::Type::FAILED_DOWNLOAD;
+ fprintf(stderr, "Image download failed for url: %s\n", it->url.c_str());
+ }
+
+ it = imageDownloadProcesses.erase(it);
+ }
+ else
+ ++it;
+ }
+ imageDownloadMutex.unlock();
+
+ while(imageDownloadProcesses.empty())
+ this_thread::sleep_for(chrono::milliseconds(20));
+ }
+ });
+ downloadWaitThread.detach();
+ }
+
+ const ImageByUrlResult Cache::getImageByUrl(const string &url, int downloadLimitBytes)
+ {
+ auto it = imageUrlCache.find(url);
+ if(it != imageUrlCache.end())
+ return it->second;
+
+ // TODO: Verify hashed url is not too long for filepath on windows
+ boost::filesystem::path filepath = getDchatDir();
+ odhtdb::Hash urlHash(url.data(), url.size());
+ filepath /= urlHash.toString();
+
+ // Check if file exists because we dont want sfml spam with "Failed to load image""...
+ if(boost::filesystem::exists(filepath))
+ {
+ try
+ {
+ const sf::Texture *texture = ResourceCache::getTexture(filepath.string());
+ lock_guard<mutex> lock(imageDownloadMutex);
+ ImageByUrlResult result { texture, ImageByUrlResult::Type::CACHED };
+ imageUrlCache[url] = result;
+ return result;
+ }
+ catch(FailedToLoadResourceException &e)
+ {
+
+ }
+ }
+
+ lock_guard<mutex> lock(imageDownloadMutex);
+ ImageByUrlResult result { nullptr, ImageByUrlResult::Type::DOWNLOADING };
+ imageUrlCache[url] = result;
+
+ string downloadLimitBytesStr = to_string(downloadLimitBytes);
+
+ Process::string_type cmd = "curl -L --silent -o '";
+ cmd += filepath.native();
+ cmd += "' --max-filesize " + downloadLimitBytesStr + " --range 0-" + downloadLimitBytesStr + " --url '" + url + "'";
+ // 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 };
+ imageDownloadProcesses.emplace_back(imageDownloadInfo);
+ return result;
+ }
+}
diff --git a/src/Channel.cpp b/src/Channel.cpp
new file mode 100644
index 0000000..0fc7ec5
--- /dev/null
+++ b/src/Channel.cpp
@@ -0,0 +1,79 @@
+#include "../include/Channel.hpp"
+#include <cstring>
+
+using namespace std;
+
+namespace dchat
+{
+ Channel::Channel() :
+ messageBoard(sf::Vector2u(1.0f, 1.0f)),
+ localOfflineUser("You")
+ {
+ {
+ Message *message = new Message(&localOfflineUser);
+ message->addText(u8"hello, worldåäö1!");
+ message->addImage("https://discordemoji.com/assets/emoji/think_fish.png");
+ messageBoard.addMessage(message);
+ }
+
+ {
+ Message *message = new Message(&localOfflineUser);
+ message->addText(u8"hello, world2!");
+ messageBoard.addMessage(message);
+ }
+
+ {
+ Message *message = new Message(&localOfflineUser);
+ message->addText(u8"hello, world3!");
+ messageBoard.addMessage(message);
+ }
+ }
+
+ Channel::~Channel()
+ {
+
+ }
+
+ void Channel::processEvent(const sf::Event &event)
+ {
+ if(event.type == sf::Event::TextEntered)
+ {
+ if(event.text.unicode == 8) // backspace
+ chatbar.removePreviousChar();
+ else if(event.text.unicode == 13) // enter
+ {
+ Message *message = new Message(&localOfflineUser);
+ auto chatbarMsgUtf8 = chatbar.getString().toUtf8();
+ string msg;
+ msg.resize(chatbarMsgUtf8.size());
+ memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size());
+
+ message->addText(msg);
+ messageBoard.addMessage(message);
+ chatbar.clear();
+ }
+ else if(event.text.unicode == 127) // delete
+ {
+ chatbar.removeNextChar();
+ }
+ else
+ {
+ chatbar.addChar(event.text.unicode);
+ }
+ }
+ else if(event.type == sf::Event::KeyPressed)
+ {
+ if(event.key.code == sf::Keyboard::Left)
+ chatbar.moveCaretLeft();
+ else if(event.key.code == sf::Keyboard::Right)
+ chatbar.moveCaretRight();
+ }
+ messageBoard.processEvent(event);
+ }
+
+ void Channel::draw(sf::RenderWindow &window, Cache &cache)
+ {
+ messageBoard.draw(window, cache);
+ chatbar.draw(window);
+ }
+}
diff --git a/src/ChannelSidePanel.cpp b/src/ChannelSidePanel.cpp
new file mode 100644
index 0000000..23693b3
--- /dev/null
+++ b/src/ChannelSidePanel.cpp
@@ -0,0 +1,9 @@
+#include "../include/ChannelSidePanel.hpp"
+
+namespace dchat
+{
+ void ChannelSidePanel::addChannel(Channel *channel)
+ {
+ channels.push_back(channel);
+ }
+}
diff --git a/src/Message.cpp b/src/Message.cpp
index 44174ec..2740c11 100644
--- a/src/Message.cpp
+++ b/src/Message.cpp
@@ -23,6 +23,11 @@ namespace dchat
messageParts.push_back(new MessagePartText(text));
}
+ void Message::addImage(const string &url)
+ {
+ messageParts.push_back(new MessagePartEmoji(url));
+ }
+
vector<MessagePart*>& Message::getParts()
{
return messageParts;
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index 2b9115a..acb7be1 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -84,7 +84,7 @@ namespace dchat
}
}
- void MessageBoard::draw(sf::RenderWindow &window)
+ void MessageBoard::draw(sf::RenderWindow &window, Cache &cache)
{
sf::RenderTarget *renderTarget = nullptr;
if(useStaticContentTexture)
@@ -99,56 +99,86 @@ namespace dchat
dirty = true;
}
- if(dirty)
+ auto renderTargetSize = renderTarget->getSize();
+
+ if(useStaticContentTexture)
{
- dirty = false;
- auto renderTargetSize = renderTarget->getSize();
-
- if(useStaticContentTexture)
+ if(dirty)
staticContentTexture.clear(BACKGROUND_COLOR);
- else
- {
- sf::RectangleShape background(sf::Vector2f(renderTargetSize.x, renderTargetSize.y));
- background.setFillColor(BACKGROUND_COLOR);
- renderTarget->draw(background);
- }
-
- const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf");
+ }
+ else
+ {
+ sf::RectangleShape background(sf::Vector2f(renderTargetSize.x, renderTargetSize.y));
+ background.setFillColor(BACKGROUND_COLOR);
+ renderTarget->draw(background);
+ }
+
+ const sf::Font &usernameFont = ResourceCache::getFont("fonts/Roboto-Regular.ttf");
+
+ sf::Vector2f position;
+ for(Message *message : messages)
+ {
+ sf::Text usernameText(message->user->getName(), usernameFont, MessagePartText::getFontSizeScaled() * 1.3f);
+ usernameText.setFillColor(sf::Color(15, 192, 252));
+ usernameText.setPosition(position);
+ if(dirty)
+ renderTarget->draw(usernameText);
+ position.y += usernameText.getCharacterSize() + USERNAME_PADDING_BOTTOM;
- sf::Vector2f position;
- for(Message *message : messages)
+ for(MessagePart *messagePart : message->getParts())
{
- sf::Text usernameText(message->user->getName(), usernameFont, MessagePartText::getFontSizeScaled() * 1.3f);
- usernameText.setFillColor(sf::Color(15, 192, 252));
- usernameText.setPosition(position);
- renderTarget->draw(usernameText);
- position.y += usernameText.getCharacterSize() + USERNAME_PADDING_BOTTOM;
-
- for(MessagePart *messagePart : message->getParts())
+ switch(messagePart->type)
{
- switch(messagePart->type)
+ case MessagePart::Type::TEXT:
{
- case MessagePart::Type::TEXT:
- {
- MessagePartText *messagePartText = static_cast<MessagePartText*>(messagePart);
- messagePartText->text.setFillColor(sf::Color(240, 240, 240));
- messagePartText->text.setCharacterSize(MessagePartText::getFontSizeScaled());
- messagePartText->text.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartText::getFontSizeScaled() * 0.5f));
+ MessagePartText *messagePartText = static_cast<MessagePartText*>(messagePart);
+ messagePartText->text.setFillColor(sf::Color(240, 240, 240));
+ messagePartText->text.setCharacterSize(MessagePartText::getFontSizeScaled());
+ messagePartText->text.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartText::getFontSizeScaled() * 0.5f));
+ if(dirty)
renderTarget->draw(messagePartText->text);
- position.x += messagePartText->text.getLocalBounds().width;
- break;
+ position.x += messagePartText->text.getLocalBounds().width;
+ break;
+ }
+ case MessagePart::Type::EMOJI:
+ {
+ MessagePartEmoji *messagePartEmoji = static_cast<MessagePartEmoji*>(messagePart);
+ auto imageByUrlResult = cache.getImageByUrl(messagePartEmoji->url, 1024 * 512);
+ position.x += 5.0f;
+ if(imageByUrlResult.texture)
+ {
+ // TODO: Verify this doesn't cause lag
+ messagePartEmoji->sprite.setTexture(*imageByUrlResult.texture, true);
+ sf::Vector2f spriteSize(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled());
+ messagePartEmoji->sprite.setScale(spriteSize.x / (float)imageByUrlResult.texture->getSize().x, spriteSize.y / (float)imageByUrlResult.texture->getSize().y);
+ messagePartEmoji->sprite.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f));
+ if(dirty)
+ renderTarget->draw(messagePartEmoji->sprite);
+ }
+ else
+ {
+ // TODO: Replace this with a loading gif
+ sf::RectangleShape emojiDownloadRect(sf::Vector2f(MessagePartEmoji::getHeightScaled(), MessagePartEmoji::getHeightScaled()));
+ emojiDownloadRect.setPosition(floor(position.x), floor(position.y + MessagePart::getSizeScaled() * 0.5f - MessagePartEmoji::getHeightScaled() * 0.5f));
+ emojiDownloadRect.setFillColor(sf::Color::White);
+ if(dirty)
+ renderTarget->draw(emojiDownloadRect);
}
+ position.x += MessagePartEmoji::getHeightScaled() + 5.0f;
+ break;
}
}
- position.x = 0.0f;
- position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM;
}
-
- if(useStaticContentTexture)
- staticContentTexture.display();
+ position.x = 0.0f;
+ position.y += MessagePart::getSizeScaled() + MESSAGE_PADDING_BOTTOM;
}
if(useStaticContentTexture)
+ staticContentTexture.display();
+
+ dirty = false;
+
+ if(useStaticContentTexture)
window.draw(sf::Sprite(staticContentTexture.getTexture()));
if(!selectingText) return;
diff --git a/src/MessagePart.cpp b/src/MessagePart.cpp
index ea8e4a5..dcef3f3 100644
--- a/src/MessagePart.cpp
+++ b/src/MessagePart.cpp
@@ -34,4 +34,28 @@ namespace dchat
{
return sf::Vector2f(text.getLocalBounds().width, getFontSizeScaled());
}
+
+ MessagePartEmoji::MessagePartEmoji(const string &_url) :
+ MessagePart(Type::EMOJI),
+ url(_url)
+ {
+
+ }
+
+ float MessagePartEmoji::getHeightScaled()
+ {
+ return MessagePart::getSizeScaled() * 1.0f;
+ }
+
+ sf::Vector2f MessagePartEmoji::getPosition() const
+ {
+ return sprite.getPosition();
+ }
+
+ sf::Vector2f MessagePartEmoji::getSize() const
+ {
+ auto spriteScale = sprite.getScale();
+ auto textureSize = sprite.getTexture()->getSize();
+ return { (float)textureSize.x * spriteScale.x, (float)textureSize.y * spriteScale.y };
+ }
}
diff --git a/src/ResourceCache.cpp b/src/ResourceCache.cpp
index 474360c..4bdd75d 100644
--- a/src/ResourceCache.cpp
+++ b/src/ResourceCache.cpp
@@ -1,12 +1,12 @@
#include "../include/ResourceCache.hpp"
#include <unordered_map>
-#include <stdexcept>
using namespace std;
namespace dchat
{
unordered_map<string, sf::Font*> fonts;
+ unordered_map<string, sf::Texture*> textures;
const sf::Font& ResourceCache::getFont(const string &filepath)
{
@@ -20,10 +20,31 @@ namespace dchat
delete font;
string errMsg = "Failed to load font: ";
errMsg += filepath;
- throw runtime_error(errMsg);
+ throw FailedToLoadResourceException(errMsg);
}
fonts[filepath] = font;
return *font;
}
+
+ const sf::Texture* ResourceCache::getTexture(const string &filepath)
+ {
+ auto it = textures.find(filepath);
+ if(it != textures.end())
+ return it->second;
+
+ 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;
+ }
}
diff --git a/src/main.cpp b/src/main.cpp
index 481f73a..284df56 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,12 +1,14 @@
-#include "../include/MessageBoard.hpp"
-#include "../include/User.hpp"
-#include "../include/Chatbar.hpp"
+#include "../include/Channel.hpp"
+#include "../include/ChannelSidePanel.hpp"
+#include "../include/Cache.hpp"
#include <string>
#include <SFML/Graphics.hpp>
#include <cstring>
+#include "process.hpp"
using namespace std;
using namespace dchat;
+using namespace TinyProcessLib;
int main()
{
@@ -14,29 +16,11 @@ int main()
window.setVerticalSyncEnabled(false);
window.setFramerateLimit(60);
- OfflineUser *localOfflineUser = new OfflineUser("You");
+ Cache cache;
- MessageBoard messageBoard(window.getSize());
-
- {
- Message *message = new Message(localOfflineUser);
- message->addText(u8"hello, world!");
- messageBoard.addMessage(message);
- }
-
- {
- Message *message = new Message(localOfflineUser);
- message->addText(u8"hello, world!");
- messageBoard.addMessage(message);
- }
-
- {
- Message *message = new Message(localOfflineUser);
- message->addText(u8"hello, world!");
- messageBoard.addMessage(message);
- }
-
- Chatbar chatbar;
+ Channel channel;
+ ChannelSidePanel channelSidePanel;
+ channelSidePanel.addChannel(&channel);
while (window.isOpen())
{
@@ -50,44 +34,11 @@ int main()
sf::View view(sf::FloatRect(0.0f, 0.0f, event.size.width, event.size.height));
window.setView(view);
}
- else if(event.type == sf::Event::TextEntered)
- {
- if(event.text.unicode == 8) // backspace
- chatbar.removePreviousChar();
- else if(event.text.unicode == 13) // enter
- {
- Message *message = new Message(localOfflineUser);
- auto chatbarMsgUtf8 = chatbar.getString().toUtf8();
- string msg;
- msg.resize(chatbarMsgUtf8.size());
- memcpy(&msg[0], chatbarMsgUtf8.data(), chatbarMsgUtf8.size());
-
- message->addText(msg);
- messageBoard.addMessage(message);
- chatbar.clear();
- }
- else if(event.text.unicode == 127) // delete
- {
- chatbar.removeNextChar();
- }
- else
- {
- chatbar.addChar(event.text.unicode);
- }
- }
- else if(event.type == sf::Event::KeyPressed)
- {
- if(event.key.code == sf::Keyboard::Left)
- chatbar.moveCaretLeft();
- else if(event.key.code == sf::Keyboard::Right)
- chatbar.moveCaretRight();
- }
- messageBoard.processEvent(event);
+ channel.processEvent(event);
}
window.clear();
- messageBoard.draw(window);
- chatbar.draw(window);
+ channel.draw(window, cache);
window.display();
}