package fetcher.model; import fetcher.controller.MainController; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.concurrent.Worker; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.image.WritableImage; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Screen; import javafx.stage.Stage; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class provides methods to take a snapshot of a given URL. * It functions through the WebView and WebEngine provided by Javafx. */ public class Snapshotter { Stage urlStage; WebView browser; WebEngine webEngine; Rectangle2D screenSize; String url; String padPath; MainController controller; /** * Builds the browser based on the given url . * @param url the URL of the website to be snapshotted. * @param controller */ public Snapshotter(String url, String padPath, MainController controller){ this.url = url; this.padPath = padPath; this.controller = controller; urlStage = new Stage(); browser = new WebView(); webEngine = browser.getEngine(); screenSize = Screen.getPrimary().getVisualBounds(); setupUI(); } /** * Takes care of the graphical aspects of the browser. */ private void setupUI() { //Creating the window and initializing the browser. urlStage.setWidth(screenSize.getWidth()); urlStage.setHeight(screenSize.getHeight()); VBox.setVgrow(browser, Priority.ALWAYS); Scene scene = new Scene(new Group()); VBox root = new VBox(); root.getChildren().addAll(browser); scene.setRoot(root); urlStage.setIconified(true); urlStage.setScene(scene); urlStage.show(); } /** * Converts a given URL to an image which will be saved on /images/ folder. * It uses a WebView which basically renders the page in the browser. Once the rendering is complete the screenshot is taken. * @return the file location. */ public String getWebsiteSnapshot(){ String fileName = buildFileNameFromUrl(); webEngine.load(url); webEngine.getLoadWorker().stateProperty().addListener(new BrowserStatusChangeListener(fileName)); //Timeout for loading pages. If the page is still loading after 20 seconds we force it to stop. Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { Platform.runLater(new Runnable() { @Override public void run() { if (webEngine.getLoadWorker().getState() == Worker.State.RUNNING) { webEngine.load(null); urlStage.close(); } } }); } },30000); return fileName; } /** * Given a BufferedImage it saves it under the specified folder. * @param destinationFolder Name of the destination folder where the image will be stored. * @param fileName Name of the file to be saved. * @param renderedImage the image to be saved. * @return true if the save has success. */ public static boolean saveImage(String destinationFolder , String fileName , BufferedImage renderedImage){ //Save the snapshot on the hard drive. File imgDir = (new File(destinationFolder + File.separator + Utils.IMAGES_SUBFOLDER)); imgDir.mkdir(); File file = new File(imgDir.getAbsolutePath() + File.separator + fileName); try { ImageIO.write(renderedImage, "png", file); return true; } catch (IOException ex) { //TODO: What to do here? ex.printStackTrace(); return false; } } /** * Creates the file name of the image based on the url. It sanitizes the input using regex so that no special characters are used. * @return the fileName built up from the url. */ private String buildFileNameFromUrl(){ //Retrieving the url string and sanitizing it for it to be saved as a File, it removes the http:// and www. from the url as well. String tempName = url.replaceAll("(http://|https://|http://www\\.|www\\.)","").replaceAll("[^a-zA-Z0-9.-]", "_"); if(tempName.length() >= 15) { //TODO: Consider grabbing the last part to distinguish the urls. Or just make something up like first 5 letters.. a dash and the last five letters. int strLength = tempName.length(); tempName = tempName.substring(strLength-15, strLength-1); } tempName += ".png"; return tempName; } /** * This class listens for browser's state change. Checks if the page has finished loading, and calls for the saveImage method. */ class BrowserStatusChangeListener implements ChangeListener<Worker.State> { String fileName; public BrowserStatusChangeListener(String name){ this.fileName = name; } @Override public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) { if (newValue.equals(Worker.State.SUCCEEDED)) { if(webEngine.getDocument().getBaseURI().equals("about:blank")) return; //If the page loads, let's take a snapshot of it. WritableImage snapshot = new WritableImage((int) screenSize.getWidth(), (int) screenSize.getHeight()); browser.snapshot(null, snapshot); BufferedImage renderedImage = SwingFXUtils.fromFXImage(snapshot, null); //Some math to get a little better thumbnail. Starting x is at 1/4 of the totale page. Goes for a width of half of the screen size to attempt and get the 'core' of the content. renderedImage = renderedImage.getSubimage((int)(screenSize.getWidth() /4),0,(int)( screenSize.getWidth() / 2 ),(int) screenSize.getHeight() / 2); saveImage(padPath, fileName, renderedImage); controller.refreshListView(); urlStage.close(); webEngine.load(null); } else if(newValue.equals(Worker.State.FAILED)){ //TODO: Send an error message somehow. urlStage.close(); } } } public static String getYoutubeThumbnail(String youtubeVideoUrl , String saveDirectory){ String pattern = "(?:videos\\/|v=)([\\w-]+)"; Pattern compiledPattern = Pattern.compile(pattern); Matcher matcher = compiledPattern.matcher(youtubeVideoUrl); if(matcher.find()){ String youtubeID = matcher.group().split("=")[1]; String thumbnailURL = "http://img.youtube.com/vi/" + youtubeID + "/0.jpg"; return Utils.saveImageFromURL(thumbnailURL,saveDirectory); } return null; } }