package com.aimmac23.node.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.exec.StreamPumper;
import org.apache.http.HttpStatus;
import org.json.JSONObject;
import com.aimmac23.node.RecordVideoCallable;
import com.aimmac23.node.VideoRecordController;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
public class VideoRecordingControlServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(VideoRecordingControlServlet.class.getName());
private static final long serialVersionUID = 1L;
private static VideoRecordController controller;
Cache<String, File> availableVideos;
// make sure we initialise the RecordVideoCallable class, because that initialises
// the native code dependencies. If we can't load the native code, we should blow
// up now
static {
try {
Class.forName(RecordVideoCallable.class.getName());
} catch (ClassNotFoundException e) {
// not possible
}
// this class contains things which should be checked at startup, not when the servlet is
// initialised
controller = new VideoRecordController();
}
public VideoRecordingControlServlet() {
super();
availableVideos = CacheBuilder.newBuilder().maximumSize(5).removalListener(new RemovalListener<String, File>() {
@Override
public void onRemoval(RemovalNotification<String, File> arg0) {
if(arg0.getValue().delete()) {
if(arg0.wasEvicted()) {
log.info("Deleted recording due to excess videos: " + arg0.getKey());
}
}
}
}).build();
addShutdownHook();
}
/**
* Deletes all videos on exit.
*/
private void addShutdownHook() {
Thread shutdownHook = new Thread(new Runnable() {
@Override
public void run() {
Iterator<String> iterator = availableVideos.asMap().keySet().iterator();
while(iterator.hasNext()) {
availableVideos.invalidate(iterator.next());
}
}
}, "Video Cache Shutdown Hook Thread");
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
service(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
service(req, resp);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String command = req.getParameter("command");
if(command == null) {
resp.setStatus(HttpStatus.SC_BAD_REQUEST);
resp.getWriter().write("Missing parameter: 'command'");
return;
}
if(command.equalsIgnoreCase("start")) {
handleStartRecording(resp);
return;
}
else if(command.equalsIgnoreCase("stop")) {
handleDoStopRecording(resp);
}
else if(command.equalsIgnoreCase("reset")) {
handleReset(resp);
}
else if(command.equalsIgnoreCase("download")) {
handleDownload(req, resp);
}
else {
resp.setStatus(HttpStatus.SC_BAD_REQUEST);
resp.getWriter().write("Bad parameter: 'command', must be either 'start', 'stop', 'reset', or 'download'");
return;
}
}
private void handleStartRecording(HttpServletResponse resp) throws IOException {
try {
controller.startRecording();
} catch (Exception e) {
resp.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write("Internal server error while trying to start recording: " + e.getMessage());
log.log(Level.SEVERE, "Caught exception while trying to start recording", e);
return;
}
resp.setStatus(HttpStatus.SC_OK);
resp.getWriter().write("Started Recording");
}
private void handleReset(HttpServletResponse resp) throws IOException {
controller.resetRecording();
resp.setStatus(HttpStatus.SC_OK);
resp.getWriter().write("OK");
}
private void handleDoStopRecording(HttpServletResponse resp) throws IOException {
try {
File videoFile = controller.stopRecording();
String fileKey = videoFile.getName();
availableVideos.put(fileKey, videoFile);
HashMap<String, Object> result = new HashMap<String, Object>();
result.put("filepath", videoFile.getCanonicalPath());
result.put("filekey", fileKey);
resp.getWriter().write(new JSONObject(result).toString());
return;
} catch (Exception e) {
resp.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write("Internal Server Error: Caught Exception: " + e.getMessage());
log.log(Level.SEVERE, "Caught exception while trying to stop recording", e);
return;
}
}
private void handleDownload(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String filekey = req.getParameter("filekey");
if(filekey == null) {
resp.setStatus(HttpStatus.SC_BAD_REQUEST);
resp.getWriter().write("Missing parameter: 'filekey'");
return;
}
File video = availableVideos.getIfPresent(filekey);
if(video == null) {
resp.setStatus(HttpStatus.SC_NOT_FOUND);
resp.getWriter().write("No video found for key: " + filekey);
return;
}
if(!video.exists()) {
resp.setStatus(HttpStatus.SC_NOT_FOUND);
resp.getWriter().write("Video file deleted for key: " + filekey);
return;
}
resp.setStatus(HttpStatus.SC_OK);
resp.setContentType("video/webm");
resp.setContentLength((int)video.length());
FileInputStream videoStream = new FileInputStream(video);
new StreamPumper(videoStream, resp.getOutputStream()).run();
log.info("Retrieved video for key: " + filekey);
}
}