package com.twasyl.slideshowfx.server.service;
import com.twasyl.slideshowfx.server.SlideshowFXServer;
import com.twasyl.slideshowfx.server.beans.chat.ChatMessage;
import com.twasyl.slideshowfx.server.beans.chat.ChatMessageAction;
import com.twasyl.slideshowfx.server.beans.chat.ChatMessageStatus;
import com.twasyl.slideshowfx.utils.ResourceHelper;
import com.twasyl.slideshowfx.utils.TemplateProcessor;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.Router;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.twasyl.slideshowfx.server.service.IServicesCode.*;
/**
* This class represents the attendee part of the internal SlideshowFX chat.
*
* @author Thierry Wasylczenko
* @version 1.0
* @since SlideshowFX 1.0
*/
public class AttendeeChatService extends AbstractSlideshowFXService {
private static final Logger LOGGER = Logger.getLogger(AttendeeChatService.class.getName());
private final Map<String, ChatMessage> chatHistory = new HashMap<>();
@Override
public void start() {
this.updateRouteMatcher();
this.register(SERVICE_CHAT_ATTENDEE_MESSAGE_ADD, buildAddMessageHandler())
.register(SERVICE_CHAT_ATTENDEE_MESSAGE_UPDATE, buildUpdateMessageHandler())
.register(SERVICE_CHAT_ATTENDEE_HISTORY, buildHistoryMessageHandler());
}
@Override
public void stop() {
this.unregisterAll();
this.chatHistory.clear();
}
private void updateRouteMatcher() {
final SlideshowFXServer singleton = SlideshowFXServer.getSingleton();
final Router router = singleton.getRouter();
// Route that get the image of an answered message
final String FONT_AWESOME_PREFIX = "/slideshowfx/font-awesome/";
router.get(FONT_AWESOME_PREFIX.concat("*")).handler(routingContext -> {
final String file = routingContext.request().path().substring(FONT_AWESOME_PREFIX.length());
try (final InputStream in = ResourceHelper.getInputStream("/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/".concat(file))) {
byte[] imageBuffer = new byte[1028];
int numberOfBytesRead;
Buffer buffer = Buffer.buffer();
while ((numberOfBytesRead = in.read(imageBuffer)) != -1) {
buffer.appendBytes(imageBuffer, 0, numberOfBytesRead);
}
routingContext.response().setChunked(true).write(buffer).end();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Can not send the font awesome css", e);
}
});
// Get the JavaScript resources
router.get("/slideshowfx/chat/js/chatService.js").handler(request -> {
final LocalMap templateTokens = this.vertx.sharedData().getLocalMap(SlideshowFXServer.SHARED_DATA_TEMPLATE_TOKENS);
final Configuration configuration = TemplateProcessor.getJsConfiguration();
final Map tokenValues = new HashMap();
tokenValues.put(templateTokens.get(SlideshowFXServer.SHARED_DATA_SERVER_HOST_TOKEN).toString(), singleton.getHost());
tokenValues.put(templateTokens.get(SlideshowFXServer.SHARED_DATA_SERVER_PORT_TOKEN).toString(), singleton.getPort() + "");
try (final StringWriter writer = new StringWriter()) {
final Template template = configuration.getTemplate("chatService.js");
template.process(tokenValues, writer);
writer.flush();
request.response().putHeader("Content-Type", "application/javascript").setStatusCode(200).setChunked(true).write(Buffer.buffer(writer.toString())).end();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error when a client tried to access the chat", e);
request.response().setStatusCode(500).end();
} catch (TemplateException e) {
LOGGER.log(Level.WARNING, "Error when processing the chat template", e);
request.response().setStatusCode(500).end();
}
});
}
private Handler<Message<JsonObject>> buildUpdateMessageHandler() {
final Handler<Message<JsonObject>> handler = message -> {
final String messageId = message.body().getJsonObject(JSON_KEY_MESSAGE).getString(JSON_KEY_MESSAGE_ID);
final ChatMessage chatMessage = this.chatHistory.get(messageId);
int responseCode;
Object responseContent;
if (chatMessage != null) {
JsonArray fields = message.body().getJsonArray(JSON_KEY_FIELDS);
fields.forEach(value -> {
if(JSON_KEY_FIELD_STATUS.equals(value)) {
chatMessage.setStatus(ChatMessageStatus.fromString(message.body().getJsonObject(JSON_KEY_MESSAGE).getString(JSON_KEY_MESSAGE_STATUS)));
} else if(JSON_KEY_FIELD_ACTION.equals(value)) {
final String action = message.body().getJsonObject(JSON_KEY_MESSAGE).getString(JSON_KEY_MESSAGE_ACTION);
chatMessage.setAction("null".equals(action) ? null : ChatMessageAction.fromString(action));
}
});
this.chatHistory.put(chatMessage.getId(), chatMessage);
final JsonObject object = this.buildResponse(SERVICE_CHAT_ATTENDEE_MESSAGE_UPDATE, RESPONSE_CODE_MESSAGE_UPDATED, chatMessage.toJSON());
this.sendResponseToWebSocketClients(object);
responseCode = RESPONSE_CODE_MESSAGE_UPDATED;
responseContent = chatMessage.toJSON();
} else {
responseCode = RESPONSE_CODE_MESSAGE_NOT_FOUND;
responseContent = new JsonObject().put(JSON_KEY_MESSAGE_ID, messageId);
}
message.reply(this.buildResponse(SERVICE_CHAT_ATTENDEE_MESSAGE_UPDATE, responseCode, responseContent));
};
return handler;
}
private Handler<Message<JsonObject>> buildAddMessageHandler() {
final Handler<Message<JsonObject>> handler = message -> {
final ChatMessage chatMessage = ChatMessage.build(message.body().encode(), null);
chatMessage.setId("msg-" + System.currentTimeMillis());
this.chatHistory.put(chatMessage.getId(), chatMessage);
final JsonObject object = this.buildResponse(SERVICE_CHAT_ATTENDEE_MESSAGE_ADD, RESPONSE_CODE_MESSAGE_ADDED, chatMessage.toJSON());
final String origin = message.body().getString(JSON_KEY_ORIGIN);
this.sendResponseToWebSocketClients(object, origin);
this.vertx.eventBus().send(SERVICE_CHAT_PRESENTER_MESSAGE_ADD, chatMessage.toJSON());
message.reply(object);
};
return handler;
}
private Handler<Message<JsonObject>> buildHistoryMessageHandler() {
final Handler<Message<JsonObject>> handler = message -> {
final JsonArray array = new JsonArray();
for(ChatMessage chatMessage : this.chatHistory.values()) {
array.add(chatMessage.toJSON());
}
message.reply(this.buildResponse(SERVICE_CHAT_ATTENDEE_HISTORY, RESPONSE_CODE_OK, array));
};
return handler;
}
}