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)
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
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")