cl.uai.webcursos.emarking.desktop.QRextractor.java Source code

Java tutorial

Introduction

Here is the source code for cl.uai.webcursos.emarking.desktop.QRextractor.java

Source

/*******************************************************************************
 * This file is part of Moodle - http://moodle.org/
 * 
 * Moodle is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Moodle is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @package cl.uai.webcursos.emarking
 * @copyright 2014 Jorge Villaln {@link http://www.villalon.cl}
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 ******************************************************************************/
/**
 * 
 */
package cl.uai.webcursos.emarking.desktop;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.imageio.ImageIO;
import javax.swing.event.EventListenerList;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.ghost4j.document.DocumentException;

import cl.uai.webcursos.emarking.desktop.data.Moodle;
import cl.uai.webcursos.emarking.desktop.utils.GhostscriptExtract;

/**
 * @author Jorge Villaln
 *
 */
public class QRextractor implements Runnable {

    /**
     * @return the fileType
     */
    public FileType getFileType() {
        return fileType;
    }

    /**
     * @param fileType the fileType to set
     */
    public void setFileType(FileType fileType) {
        this.fileType = fileType;
    }

    public enum FileType {
        ZIP, PDF
    }

    private FileType fileType;

    private static Logger logger = Logger.getLogger(QRextractor.class);

    private File tempdir = null;
    private int totalpages = 0;
    private int maxpages = Integer.MAX_VALUE;
    private int threads = 4;
    private int step = 32;
    private int resolution = 300;

    public int getResolution() {
        return resolution;
    }

    public void setResolution(int resolution) {
        this.resolution = resolution;
    }

    private boolean doubleside = false;

    private String pdffile = null;

    private TreeMap<Integer, Map<Integer, String>> decodedpages = null;

    private TreeMap<Integer, String> errorpages = null;

    private EventListenerList listenerList = null;

    private Moodle moodle;

    public QRextractor(Moodle _moodle) {
        this.listenerList = new EventListenerList();
        this.decodedpages = new TreeMap<Integer, Map<Integer, String>>();
        this.errorpages = new TreeMap<Integer, String>();
        this.moodle = _moodle;
    }

    /**
     * Adds a listener for the QR decoded event
     * @param l
     */
    void addPageProcessedListener(PageProcessedListener l) {
        listenerList.add(PageProcessedListener.class, l);
    }

    /**
     * Removes a listener for the QR decoded event
     * @param l
     */
    void removePageProcessedListener(PageProcessedListener l) {
        listenerList.remove(PageProcessedListener.class, l);
    }

    protected void firePageProcessed(QRExtractorEvent e) {
        PageProcessedListener[] ls = listenerList.getListeners(PageProcessedListener.class);
        for (PageProcessedListener l : ls) {
            l.processed(e);
        }
    }

    protected void fireExtractionFinished(QRExtractorEvent e) {
        PageProcessedListener[] ls = listenerList.getListeners(PageProcessedListener.class);
        for (PageProcessedListener l : ls) {
            l.finished(e);
        }
    }

    protected void fireExtractionStarted(QRExtractorEvent e) {
        PageProcessedListener[] ls = listenerList.getListeners(PageProcessedListener.class);
        for (PageProcessedListener l : ls) {
            l.started(e);
        }
    }

    /**
     * @return the decodedpages
     */
    public TreeMap<Integer, Map<Integer, String>> getDecodedpages() {
        return decodedpages;
    }

    /**
     * @return the errorpages
     */
    public TreeMap<Integer, String> getErrorpages() {
        return errorpages;
    }

    /**
     * @return the maxpages
     */
    public int getMaxpages() {
        return maxpages;
    }

    public Object[][] getResultsAsData() {
        Object[][] data = new Object[totalpages][2];

        for (int i = 0; i < totalpages; i++) {
            data[i][0] = i + 1;
            data[i][1] = "N/A";
        }

        return data;
    }

    /**
     * @return the step
     */
    public int getStep() {
        return step;
    }

    /**
     * @return the tempdir
     */
    public File getTempdir() {
        return tempdir;
    }

    /**
     * @return the totalpages
     */
    public int getTotalpages() {
        return totalpages;
    }

    /**
     * @return the doubleside
     */
    public boolean isDoubleside() {
        return doubleside;
    }

    void removeQRdecodeListener(PageProcessedListener l) {
        listenerList.remove(PageProcessedListener.class, l);
    }

    /**
     * 
     * @param pdfdoc
     * @param totalpages
     * @param tempdir
     * @throws DocumentException 
     */
    @Override
    public void run() {

        logger.debug("Starting Ghost4j");

        logger.debug("Document: " + this.pdffile);
        logger.debug("Total pages: " + totalpages);
        logger.debug("Tempdir: " + this.tempdir);
        logger.debug("Resolution: " + this.resolution + "ppp");
        logger.debug("Maxpages: " + this.maxpages);
        logger.debug("Threads: " + this.threads);
        logger.debug("Doubleside: " + this.doubleside);
        logger.debug("Step: " + this.step);
        logger.debug("File type: " + this.fileType);

        long time = System.currentTimeMillis();
        int currentpage = 0;

        try {
            File f = new File(this.pdffile);

            if (!f.exists()) {
                throw new Exception("Invalid PDF file for processing. It does not exist.");
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getLocalizedMessage());
            return;
        }

        // Checking if number of pages is even for double sided scanning jobs
        if (doubleside && step % 2 != 0)
            step++;

        // Checking that maxpages is also even for double sided scanning jobs
        if (doubleside && maxpages % 2 != 0 && maxpages < totalpages)
            maxpages++;

        if (totalpages > maxpages)
            totalpages = maxpages;

        while (totalpages > currentpage && currentpage < maxpages) {

            if (Thread.currentThread().isInterrupted())
                break;

            // Measuring time per step
            long timeperstep = System.currentTimeMillis();

            // Calculate last page index, either the current page plus a step or the total pages 
            int lastpage = Math.min(currentpage + step - 1, totalpages - 1);
            logger.debug("Extracting images from " + (currentpage + 1) + " to " + (lastpage + 1));

            // We start with an empty array for images
            List<BufferedImage> images = null;
            // Images are exracted using the SimpleRenderer from Ghost4j
            try {
                images = getImages(currentpage + 1, lastpage + 1, pdffile, this.tempdir);
                long timeperstepextract = System.currentTimeMillis() - timeperstep;
                logger.debug("Extraction of " + images.size() + " pages finished in " + (timeperstepextract / 1000)
                        + " seconds");
            } catch (Exception e3) {
                e3.printStackTrace();
                break;
            }

            // As we don't really know how many pages will Ghost4j extract
            lastpage = currentpage + images.size();

            // Iterate through extracted images
            for (int i = 0; i < images.size(); i += threads) {

                // Checks if a cancel button was pressed while processing, in which case we stop
                if (Thread.currentThread().isInterrupted())
                    break;

                ImageDecoder[] decoders = new ImageDecoder[threads];
                Thread[] decoderthreads = new Thread[threads];

                for (int j = 0; j < threads; j++) {
                    // This is the current page
                    int filenumber = currentpage;

                    // Checks if a cancel button was pressed while processing, in which case we stop
                    if (Thread.currentThread().isInterrupted())
                        break;

                    if (i + j >= images.size())
                        break;

                    BufferedImage currentpageimage = (BufferedImage) images.get(i + j);
                    BufferedImage nextpageimage = null;
                    if (doubleside) {
                        nextpageimage = currentpage <= lastpage && images.size() > i + j + 1
                                ? (BufferedImage) images.get(i + j + 1)
                                : null;
                    }

                    decoders[j] = new ImageDecoder(currentpageimage, nextpageimage, filenumber, tempdir,
                            this.moodle);

                    decoderthreads[j] = new Thread(decoders[j]);
                    decoderthreads[j].start();

                    currentpage++;

                    if (doubleside) {
                        currentpage++;
                        i++;
                    }
                }

                for (int j = 0; j < threads; j++) {
                    try {
                        if (decoderthreads[j] != null)
                            decoderthreads[j].join();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }

                for (int j = 0; j < threads; j++) {

                    // Checks if a cancel button was pressed while processing, in which case we stop
                    if (Thread.currentThread().isInterrupted())
                        break;

                    ImageDecoder decoder = decoders[j];
                    if (decoder == null)
                        continue;

                    QrDecodingResult result = decoder.getQrResult();
                    if (decoder.isSuccess()) {
                        Map<Integer, String> pages = null;
                        if (decodedpages.containsKey(result.getUserid())) {
                            pages = decodedpages.get(result.getUserid());
                        } else {
                            pages = new Hashtable<Integer, String>();
                            decodedpages.put(result.getUserid(), pages);
                        }
                        pages.put(result.getExampage(), result.getFilename());
                        if (doubleside) {
                            pages.put(result.getExampage(), result.getBackfilename());
                        }
                    } else {
                        errorpages.put(decoder.getFilenumber(), result.getFilename());
                        if (doubleside)
                            errorpages.put(decoder.getFilenumber(), result.getBackfilename());
                    }

                    QRExtractorEvent e = new QRExtractorEvent(this, decoder.getQrResult(), false);
                    firePageProcessed(e);

                    if (doubleside) {
                        QRExtractorEvent e2 = new QRExtractorEvent(this, decoder.getQrResult(), true);
                        firePageProcessed(e2);
                    }
                }
            }

            timeperstep = System.currentTimeMillis() - timeperstep;
            logger.debug("Extraction + Processing finished in " + (timeperstep / 1000) + " seconds");
        }

        time = System.currentTimeMillis() - time;
        logger.debug("Total Extraction finished in " + (time / 1000) + " seconds");

        logger.debug("Decoded pages:" + decodedpages.size());
        logger.debug("Error pages:" + errorpages.size());

        QRExtractorEvent e = new QRExtractorEvent(this, null, false);
        fireExtractionFinished(e);
    }

    /**
     * @param doubleside the doubleside to set
     */
    public void setDoubleside(boolean doubleside) {
        this.doubleside = doubleside;
        this.setStep();
    }

    /**
     * @param maxpages the maxpages to set
     */
    public void setMaxpages(int maxpages) {
        this.maxpages = maxpages;
    }

    /**
     * @param tempdir the tempdir to set
     * @throws IOException 
     */
    public void setTempdir(File tmpdir) throws IOException {
        if (tmpdir == null) {
            File tempdir = File.createTempFile("emarking", Long.toString(System.nanoTime()));
            if (!tempdir.delete()) {
                logger.error("Could not delete temp dir");
                System.exit(1);
            }
            if (!tempdir.mkdir()) {
                logger.error("Could not create temp dir");
                System.exit(1);
            }
            tmpdir = tempdir;
        }
        if (!tmpdir.exists() || !tmpdir.isDirectory()) {
            logger.error("Invalid temp dirextory " + tmpdir.getAbsolutePath());
            System.exit(1);
        }
        if (tmpdir.listFiles().length != 0) {
            logger.debug("Temp folder is not empty, cleaning.");
            FileUtils.cleanDirectory(tmpdir);
        }
        if (this.tempdir != null) {
            this.tempdir.delete();
        }
        this.tempdir = tmpdir;
    }

    public String getTempdirStringPath() {
        return this.tempdir.getAbsolutePath();
    }

    /**
     * @param totalpages the totalpages to set
     */
    public void setTotalpages(int totalpages) {
        this.totalpages = totalpages;
    }

    private List<BufferedImage> getImages(int first, int last, String pdffile, File tempdir) throws Exception {

        // If we are processing a PDF file we use ghostscript to read it
        if (this.fileType == FileType.PDF) {

            GhostscriptExtract.extractImagesFromPDF(first, last, this.resolution, tempdir, pdffile);

            List<BufferedImage> images = new ArrayList<BufferedImage>();

            for (int i = 1; i <= last - first + 1; i++) {
                try {
                    File f = new File(
                            tempdir.getAbsolutePath() + "/page" + String.format("%06d", i) + Moodle.imageExtension);
                    images.add(ImageIO.read(f));
                    f.delete();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            return images;
        } else if (this.fileType == FileType.ZIP) {

            List<BufferedImage> images = new ArrayList<BufferedImage>();

            for (int i = first; i <= last; i++) {
                String filename = this.tempdir.getAbsolutePath() + "/Prueba_" + i + Moodle.imageExtension;
                try {
                    File f = new File(filename);
                    images.add(ImageIO.read(f));
                    f.delete();
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.error("Could not read file " + filename);
                }
            }

            return images;
        } else {
            throw new Exception("Invalid file type");
        }
    }

    public String getPdffile() {
        return pdffile;
    }

    public void setPdffile(String pdffile) {
        this.pdffile = pdffile;
    }

    public int getMaxThreads() {
        return this.threads;
    }

    public void setMaxThreads(int _threads) {
        this.threads = _threads;
        this.setStep();
    }

    private void setStep() {
        this.step = this.doubleside ? this.threads * 4 : this.threads * 2;
    }
}