package com.aimmac23.hub.videostorage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.exec.StreamPumper; /** * A plugin to store permamently videos in a local filesystem directory. * * This should be used when: * <ul> * <li>You don't want your Selenium Grid to delete videos</li> * <li>You don't want your Selenium Grid to forget about videos over restarts</li> * <li>You are trying to integrate with something that needs to access videos directly * (NOT through the Hub), and you so happen to share a filesystem with that thing. </li> * </ul> * * @author Alasdair Macmillan * */ public class LocalFileVideoStore implements IVideoStore { private static final Logger log = Logger.getLogger(LocalFileVideoStore.class.getName()); private File directory; public LocalFileVideoStore() { String path = System.getProperty("video.path"); if(path == null) { throw new IllegalArgumentException("'video.path' is not defined - " + "you need to pass -Dvideo.path=<directory> to use " + this.getClass().getName()); } directory = new File(path); if(!directory.exists()) { directory.mkdirs(); } if(!directory.exists()) { throw new IllegalStateException("Target directory does not exist: " + directory); } if(!directory.isDirectory()) { throw new IllegalStateException("Target directory is not a directory: " + directory); } if(!directory.canWrite()) { throw new IllegalStateException("Target directory is now writeable: " + directory); } } @Override public void storeVideo(InputStream videoStream, long contentLength, String mimeType, String sessionId, SessionInfoBean infoBean) throws Exception { File target = new File(directory, sessionId + ".webm"); FileOutputStream fileStream = new FileOutputStream(target); try { new StreamPumper(videoStream, fileStream).run(); } finally { fileStream.close(); } log.info("Successfully written video file for session to: " + target); } @Override public LocalFileVideoStoreDownloadContext retrieveVideo(String sessionId) throws Exception { File target = new File(directory, sessionId + ".webm"); if(target.exists() && target.isFile() && target.canRead()) { return new LocalFileVideoStoreDownloadContext(target); } log.info("File not found, or is not readable for sessionId: " + sessionId); return new LocalFileVideoStoreDownloadContext(null); } @Override public StoredVideoInfoContext getVideoInformation(String sessionId) throws Exception { return retrieveVideo(sessionId); } @Override public String getVideoStoreTypeIdentifier() { return "LOCAL_FILE"; } private static class LocalFileVideoStoreDownloadContext implements StoredVideoDownloadContext, StoredVideoInfoContext { private File file; private FileInputStream stream; private String canonicalPath; public LocalFileVideoStoreDownloadContext(File file) throws FileNotFoundException, IOException { this.file = file; if(file != null && file.exists()) { stream = new FileInputStream(file); this.canonicalPath = file.getCanonicalPath(); } else { stream = null; canonicalPath = null; } } @Override public boolean isVideoFound() { return file != null; } @Override public InputStream getStream() throws IOException { return stream; } @Override public Long getContentLengthIfKnown() { return new Long(file.length()); } @Override public void close() { if(stream == null) { // nothing to do return; } try { stream.close(); } catch (IOException e) { log.log(Level.WARNING, "Could not close file: " + file, e); } } @Override public Map<String, Object> additionalInformation() { return new HashMap<String, Object>(Collections.singletonMap("path", canonicalPath)); } } }