Esempio n. 1
0
    def execute(self, entry):
        """Print a print job.

        This is the core of PrintingService.

        entry (QueueEntry): the entry containing the operation to
            perform.

        """
        # TODO: automatically re-enqueue in case of a recoverable
        # error.
        printjob_id = entry.item.printjob_id
        with SessionGen() as session:
            # Obtain print job.
            printjob = PrintJob.get_from_id(printjob_id, session)
            if printjob is None:
                raise ValueError("Print job %d not found in the database." %
                                 printjob_id)
            user = printjob.participation.user
            contest = printjob.participation.contest
            timezone = get_timezone(user, contest)
            timestr = format_datetime(printjob.timestamp, timezone)
            filename = printjob.filename

            # Check if it's ready to be printed.
            if printjob.done:
                logger.info("Print job %d was already sent to the printer.",
                            printjob_id)

            directory = tempfile.mkdtemp(dir=config.temp_dir)
            logger.info("Preparing print job in directory %s", directory)

            # Take the base name just to be sure.
            relname = "source_" + os.path.basename(filename)
            source = os.path.join(directory, relname)
            with open(source, "wb") as file_:
                self.file_cacher.get_file_to_fobj(printjob.digest, file_)

            if filename.endswith(".pdf") and config.pdf_printing_allowed:
                source_pdf = source
            else:
                # Convert text to ps.
                source_ps = os.path.join(directory, "source.ps")
                cmd = ["a2ps",
                       source,
                       "--delegate=no",
                       "--output=" + source_ps,
                       "--medium=%s" % config.paper_size.capitalize(),
                       "--portrait",
                       "--columns=1",
                       "--rows=1",
                       "--pages=1-%d" % (config.max_pages_per_job),
                       "--header=",
                       "--footer=",
                       "--left-footer=",
                       "--right-footer=",
                       "--center-title=" + filename,
                       "--left-title=" + timestr]
                ret = subprocess.call(cmd, cwd=directory)
                if ret != 0:
                    raise Exception(
                        "Failed to convert text file to ps with command: %s"
                        "(error %d)" % (pretty_print_cmdline(cmd), ret))

                if not os.path.exists(source_ps):
                    logger.warning("Unable to convert from text to ps.")
                    printjob.done = True
                    printjob.status = json.dumps([
                        N_("Invalid file")])
                    session.commit()
                    rmtree(directory)
                    return

                # Convert ps to pdf
                source_pdf = os.path.join(directory, "source.pdf")
                cmd = ["ps2pdf",
                       "-sPAPERSIZE=%s" % config.paper_size.lower(),
                       source_ps]
                ret = subprocess.call(cmd, cwd=directory)
                if ret != 0:
                    raise Exception(
                        "Failed to convert ps file to pdf with command: %s"
                        "(error %d)" % (pretty_print_cmdline(cmd), ret))

            # Find out number of pages
            with open(source_pdf, "rb") as file_:
                pdfreader = PdfFileReader(file_)
                page_count = pdfreader.getNumPages()

            logger.info("Preparing %d page(s) (plus the title page)",
                        page_count)

            if page_count > config.max_pages_per_job:
                logger.info("Too many pages.")
                printjob.done = True
                printjob.status = json.dumps([
                    N_("Print job has too many pages")])
                session.commit()
                rmtree(directory)
                return

            # Add the title page
            title_tex = os.path.join(directory, "title_page.tex")
            title_pdf = os.path.join(directory, "title_page.pdf")
            with open(title_tex, "w") as f:
                f.write(self.template_loader.load("title_page.tex")
                        .generate(user=user, filename=filename,
                                  timestr=timestr,
                                  page_count=page_count,
                                  paper_size=config.paper_size))
            cmd = ["pdflatex",
                   "-interaction",
                   "nonstopmode",
                   title_tex]
            ret = subprocess.call(cmd, cwd=directory)
            if ret != 0:
                raise Exception(
                    "Failed to create title page with command: %s"
                    "(error %d)" % (pretty_print_cmdline(cmd), ret))

            pdfmerger = PdfFileMerger()
            with open(title_pdf, "rb") as file_:
                pdfmerger.append(file_)
            with open(source_pdf, "rb") as file_:
                pdfmerger.append(file_)
            result = os.path.join(directory, "document.pdf")
            with open(result, "wb") as file_:
                pdfmerger.write(file_)

            try:
                printer_connection = cups.Connection()
                printer_connection.printFile(
                    config.printer, result,
                    "Printout %d" % printjob_id, {})
            except cups.IPPError as error:
                logger.error("Unable to print: `%s'.", error)
            else:
                printjob.done = True
                printjob.status = json.dumps([N_("Sent to printer")])
                session.commit()
            finally:
                rmtree(directory)
Esempio n. 2
0
def accept_print_job(sql_session, file_cacher, participation, timestamp, files):
    """Add a print job to the database.

    This function receives the values that a contestant provides to CWS
    when they request a printout, it validates them and, if there are
    no issues, stores the files and creates a PrintJob in the database.

    sql_session (Session): the SQLAlchemy database session to use.
    file_cacher (FileCacher): the file cacher to store the files.
    participation (Participation): the contestant who sent the request.
    timestamp (datetime): the moment at which the request occurred.
    files ({str: [HTTPFile]}): the provided files, as a dictionary
        whose keys are the field names and whose values are lists of
        Tornado HTTPFile objects (each with a filename and a body
        attribute). The expected format consists of one item, whose key
        is "file" and whose value is a singleton list.

    return (PrintJob): the PrintJob that was added to the database.

    raise (PrintingDisabled): if printing is disabled because there are
        no printers available).
    raise (UnacceptablePrintJob): if some of the requirements that have
        to be met in order for the request to be accepted don't hold.

    """

    if config.printer is None:
        raise PrintingDisabled()

    old_count = sql_session.query(func.count(PrintJob.id)) \
        .filter(PrintJob.participation == participation).scalar()
    if config.max_jobs_per_user <= old_count:
        raise UnacceptablePrintJob(
            N_("Too many print jobs!"),
            N_("You have reached the maximum limit of at most %d print jobs."),
            config.max_jobs_per_user)

    if len(files) != 1 or "file" not in files or len(files["file"]) != 1:
        raise UnacceptablePrintJob(
            N_("Invalid format!"),
            N_("Please select the correct files."))

    filename = files["file"][0].filename
    data = files["file"][0].body

    if len(data) > config.max_print_length:
        raise UnacceptablePrintJob(
            N_("File too big!"),
            N_("Each file must be at most %d bytes long."),
            config.max_print_length)

    try:
        digest = file_cacher.put_file_content(
            data, "Print job sent by %s at %s." % (participation.user.username,
                                                   timestamp))

    except Exception as error:
        logger.error("Storage failed! %s", error)
        raise UnacceptablePrintJob(
            N_("Print job storage failed!"),
            N_("Please try again."))

    # The file is stored, ready to submit!
    logger.info("File stored for print job sent by %s",
                participation.user.username)

    printjob = PrintJob(timestamp=timestamp,
                        participation=participation,
                        filename=filename,
                        digest=digest)
    sql_session.add(printjob)

    return printjob
Esempio n. 3
0
    def post(self):
        participation = self.current_user

        if not self.r_params["printing_enabled"]:
            self.redirect("/")
            return

        printjobs = self.sql_session.query(PrintJob)\
            .filter(PrintJob.participation == participation)\
            .all()
        old_count = len(printjobs)
        if config.max_jobs_per_user <= old_count:
            self.application.service.add_notification(
                participation.user.username,
                self.timestamp,
                self._("Too many print jobs!"),
                self._("You have reached the maximum limit of "
                       "at most %d print jobs.") % config.max_jobs_per_user,
                NOTIFICATION_ERROR)
            self.redirect("/printing")
            return

        # Ensure that the user did not submit multiple files with the
        # same name and that the user sent exactly one file.
        if any(len(filename) != 1
               for filename in self.request.files.values()) \
                or set(self.request.files.keys()) != set(["file"]):
            self.application.service.add_notification(
                participation.user.username,
                self.timestamp,
                self._("Invalid format!"),
                self._("Please select the correct files."),
                NOTIFICATION_ERROR)
            self.redirect("/printing")
            return

        filename = self.request.files["file"][0]["filename"]
        data = self.request.files["file"][0]["body"]

        # Check if submitted file is small enough.
        if len(data) > config.max_print_length:
            self.application.service.add_notification(
                participation.user.username,
                self.timestamp,
                self._("File too big!"),
                self._("Each file must be at most %d bytes long.") %
                config.max_print_length,
                NOTIFICATION_ERROR)
            self.redirect("/printing")
            return

        # We now have to send the file to the destination...
        try:
            digest = self.application.service.file_cacher.put_file_content(
                data,
                "Print job sent by %s at %d." % (
                    participation.user.username,
                    make_timestamp(self.timestamp)))

        # In case of error, the server aborts
        except Exception as error:
            logger.error("Storage failed! %s", error)
            self.application.service.add_notification(
                participation.user.username,
                self.timestamp,
                self._("Print job storage failed!"),
                self._("Please try again."),
                NOTIFICATION_ERROR)
            self.redirect("/printing")
            return

        # The file is stored, ready to submit!
        logger.info("File stored for print job sent by %s",
                    participation.user.username)

        printjob = PrintJob(timestamp=self.timestamp,
                            participation=participation,
                            filename=filename,
                            digest=digest)

        self.sql_session.add(printjob)
        self.sql_session.commit()
        self.application.service.printing_service.new_printjob(
            printjob_id=printjob.id)
        self.application.service.add_notification(
            participation.user.username,
            self.timestamp,
            self._("Print job received"),
            self._("Your print job has been received."),
            NOTIFICATION_SUCCESS)
        self.redirect("/printing")