package org.skylion.mangareader.mangaengine; import java.awt.BorderLayout; import java.awt.Container; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Closeable; import java.io.IOException; import java.util.List; import javax.swing.JProgressBar; import javax.swing.SwingWorker; import org.skylion.mangareader.util.Logger; /** * A class that wraps a MangaEngine and prefetches from it to improve speed. * The class also adds a progress bar to the JFrame to show how the prefetching is going. * @author Skylion * */ public class Prefetcher implements MangaEngine, Closeable{ private MangaEngine mangaEngine;//Current MangaEngine private BufferedImage[] pages;//Current Images private String[] pageURLs; //URLs corresponding to current images private String mangaName;//CurrentMangaName private JProgressBar progressBar;//The Progress Monitor private Container parent; //The Parent Component to display the loading bar in private Task task;//The Swing Worker that handles repaint /** * Constructor * @param window The JFrame you want to add the progressbar to * @param mangaEngine The Engine you want to prefetch from */ public Prefetcher(Container window, MangaEngine mangaEngine){ this.mangaEngine = mangaEngine; parent = window; mangaName = mangaEngine.getMangaName(); pageURLs = mangaEngine.getPageList(); progressBar = new JProgressBar(0, mangaEngine.getPageList().length); prefetch(); } /** * Performs the prefetching */ public void prefetch(){ mangaName = mangaEngine.getMangaName(); pageURLs = mangaEngine.getPageList(); pages = new BufferedImage[pageURLs.length]; progressBar.setValue(0); progressBar.setMaximum(pageURLs.length); progressBar.setStringPainted(true); if(task!=null && !task.isDone() && !task.isCancelled()){//Cancels previous task before starting a new one. task.cancel(true); } task = new Task(); task.addPropertyChangeListener(new PropertyChangeListener(){ /** * Invoked when a task's progress property changes. */ @Override public void propertyChange(PropertyChangeEvent evt) {//Updates Progressbar if ("progress".equals(evt.getPropertyName()) ) { int progress = (Integer) evt.getNewValue(); parent.repaint(); progressBar.setValue(progress); progressBar.setString("Loading Page: " + progress + " of " + progressBar.getMaximum()); } } }); task.execute(); } /** * Checks solely whether or not the URL is in the database. * @param URL The URL you want to check * @return True if in database false otherwise. */ private boolean isCached(String URL){ for(int i = 0; i<pageURLs.length; i++){ if(pageURLs[i].equals(URL) && pages[i]!=null){ return true; } } return false; } /** * Checks if url is in database. * @param URL The URL You want to check * @return True if the URL is found, false otherwise. */ private boolean isFetched(String URL){ for(int i = 0; i<pageURLs.length; i++){ if(pageURLs[i].equals(URL)){ return true; } } System.out.println("Prefetching because of " + URL); return false; } /** * Fetches the data * @param URL * @return */ private BufferedImage fetch(String URL){ if(!isCached(URL) || mangaEngine.getCurrentPageNum()>pages.length){ try { BufferedImage icon = mangaEngine.loadImg(URL); if(!isFetched(URL)){ prefetch(); } if(icon==null){//Sometimes the chapter ends or starts on a blank page. icon = mangaEngine.loadImg(mangaEngine.getNextPage()); } return icon; } catch (IOException e) { Logger.log(e); return null; } } else { return pages[mangaEngine.getCurrentPageNum()-1]; } } @Override public String getCurrentURL() { return mangaEngine.getCurrentURL(); } @Override public void setCurrentURL(String url){ mangaEngine.setCurrentURL(url); } @Override public BufferedImage loadImg(String url) throws IOException { if(url == null) { return null; } if(isCached(url)) { mangaEngine.setCurrentURL(url); return fetch(url); } else { BufferedImage out = mangaEngine.loadImg(url); if(!isFetched(url)) { prefetch(); } return out; } } @Override public BufferedImage getImage(String url) throws IOException { return mangaEngine.getImage(url); } @Override public String getNextPage() { String currentURL = mangaEngine.getCurrentURL(); String nextPage = mangaEngine.getNextPage(); if(isCached(nextPage) || task == null || task.isCancelled() || task.isDone()){ return nextPage; } else{ return currentURL; } } @Override public String getPreviousPage() { return mangaEngine.getPreviousPage(); } @Override public boolean isValidPage(String url) { return mangaEngine.isValidPage(url); } @Override public List<String> getMangaList() { return mangaEngine.getMangaList(); } @Override public String getMangaName() { return mangaName; } @Override public String[] getChapterList() { return mangaEngine.getChapterList(); } @Override public String[] getPageList() { return pageURLs; } @Override public String getMangaURL(String mangaName) { return mangaEngine.getMangaURL(mangaName); } /** * Returns the MangaEngine that the class wraps * @return The wrapped MangaEngine */ public MangaEngine getMangaEngine(){ return mangaEngine; } @Override public int getCurrentPageNum() { return mangaEngine.getCurrentPageNum(); } @Override public int getCurrentChapNum() { return mangaEngine.getCurrentChapNum(); } @Override public String[] getChapterNames() { return mangaEngine.getChapterNames(); } @Override public void close(){ if(task!=null && !task.isDone() && !task.isCancelled()){//Cancels previous task before starting a new one. task.cancel(true); } } /** * Where the actual prefetching happens * @author Skylion */ class Task extends SwingWorker<Void, Void> { public Task(){ parent.add(progressBar, BorderLayout.SOUTH);//Adds ProgressBar to bottom parent.revalidate();//Refreshes JFRame parent.repaint(); } /** * Main task. Executed in background thread. */ @Override public Void doInBackground() { for(int i = 0; i<pageURLs.length && !this.isCancelled(); i++){ int attemptNum = 0; while(attemptNum <=3){//Retries three times to load the image. try { pages[i] = mangaEngine.getImage(pageURLs[i]);//Loads image progressBar.setValue(i);//Updates progressbar progressBar.setString("Loading Page: " + (i+1) + " of " + progressBar.getMaximum()); break; } catch(IOException e){ Logger.log(e); attemptNum++; } } } return null; } /** * Executed in event dispatching thread */ @Override public void done() { super.done(); //Cleans up parent.remove(progressBar); //Removes progressbar parent.revalidate(); //Refreshes JFrame parent.repaint(); } } }