Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add some more flexibility #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ippserver/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ def parse_args(args=None):

parser_save = parser_action.add_parser('save', help='Write any print jobs to disk')
parser_save.add_argument('--pdf', action='store_true', default=False, help=pdf_help)
parser_save.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file')
parser_save.add_argument('directory', metavar='DIRECTORY', help='Directory to save files into')

parser_command = parser_action.add_parser('run', help='Run a command when recieving a print job')
parser_command.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND', help='Command to run')
parser_command.add_argument('--pdf', action='store_true', default=False, help=pdf_help)
parser_command.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file')
parser_command.add_argument('--env', action='store_true', default=False, help="Store Job attributes in environment (IPP_JOB_ATTRIBUTES)")

parser_saverun = parser_action.add_parser('saveandrun', help='Write any print jobs to disk and the run a command on them')
parser_saverun.add_argument('--pdf', action='store_true', default=False, help=pdf_help)
parser_saverun.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file')
parser_saverun.add_argument('--env', action='store_true', default=False, help="Store Job attributes in environment (IPP_JOB_ATTRIBUTES)")
parser_saverun.add_argument('directory', metavar='DIRECTORY', help='Directory to save files into')
parser_saverun.add_argument('command', nargs=argparse.REMAINDER, metavar='COMMAND', help='Command to run (the filename will be added at the end)')
Expand All @@ -45,6 +48,7 @@ def parse_args(args=None):

parser_command = parser_action.add_parser('pc2paper', help='Post print jobs using http://www.pc2paper.org/')
parser_command.add_argument('--pdf', action='store_true', default=False, help=pdf_help)
parser_command.add_argument('--ppd', type=str, metavar='PPD', help='Name of the PPD file')
parser_command.add_argument('--config', metavar='CONFIG', help='File containing an address to send to, in json format')
parser_loader = parser_action.add_parser('load', help='Load own behaviour')
parser_loader.add_argument('path', nargs=1, metavar=['PATH'], help='Module implementing behaviour')
Expand All @@ -56,22 +60,30 @@ def parse_args(args=None):
def behaviour_from_parsed_args(args):
if args.action == 'save':
return behaviour.SaveFilePrinter(
uri=args.host + ":" + str(args.port),
ppdfile=args.ppd,
directory=args.directory,
filename_ext='pdf' if args.pdf else 'ps')
if args.action == 'run':
return behaviour.RunCommandPrinter(
uri=args.host + ":" + str(args.port),
ppdfile=args.ppd,
command=args.command,
use_env=args.env,
filename_ext='pdf' if args.pdf else 'ps')
if args.action == 'saveandrun':
return behaviour.SaveAndRunPrinter(
uri=args.host + ":" + str(args.port),
ppdfile=args.ppd,
command=args.command,
use_env=args.env,
directory=args.directory,
filename_ext='pdf' if args.pdf else 'ps')
if args.action == 'pc2paper':
pc2paper_config = Pc2Paper.from_config_file(args.config)
return behaviour.PostageServicePrinter(
uri=args.host + ":" + str(args.port),
ppdfile=args.ppd,
service_api=pc2paper_config,
filename_ext='pdf' if args.pdf else 'ps')
if args.action == 'load':
Expand Down
82 changes: 53 additions & 29 deletions ippserver/behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .constants import (
JobStateEnum, OperationEnum, StatusCodeEnum, SectionEnum, TagEnum
)
from .ppd import BasicPostscriptPPD, BasicPdfPPD
from .ppd import BasicPostscriptPPD, BasicPdfPPD, FilePPD
from .request import IppRequest


Expand Down Expand Up @@ -48,13 +48,21 @@ def prepare_environment(ipp_request):
return env


def env(var):
"""Get (bytes) value from environment"""
value = os.environ.get(var)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the mix of argparse and os.environ, as it gives multiple ways to configure things at the same time.

But I can't think of another way to do this. (I think the "argparse kool aid" way would be to have a classmethod on the Behaviour which returns the subparser it would like? That feels over-complicated.)

So there's no good reason not do do it like this. Especially as the impact of forgetting to set IPP_PRINTER_NAME is very low.

if value:
return value.encode('utf-8')


class Behaviour(object):
"""Do anything in response to IPP requests"""
version = (1, 1)
base_uri = b'ipp://localhost:1234/'
printer_uri = b'ipp://localhost:1234/printer'

def __init__(self, ppd=BasicPostscriptPPD()):
def __init__(self, uri="localhost:1234", ppd=BasicPostscriptPPD()):
self.uri = uri.encode("utf-8")
self.base_uri = b'ipp://' + self.uri
self.printer_uri = b'ipp://' + self.uri + b'/printer'
self.ppd = ppd

def expect_page_data_follows(self, ipp_request):
Expand Down Expand Up @@ -225,17 +233,17 @@ def printer_list_attributes(self):
SectionEnum.printer,
b'printer-name',
TagEnum.name_without_language
): [b'ipp-printer.py'],
): [env("IPP_PRINTER_NAME") or b'ipp-printer.py'],
(
SectionEnum.printer,
b'printer-info',
TagEnum.text_without_language
): [b'Printer using ipp-printer.py'],
): [env("IPP_PRINTER_INFO") or b'Printer using ipp-printer.py'],
(
SectionEnum.printer,
b'printer-make-and-model',
TagEnum.text_without_language
): [b'h2g2bob\'s ipp-printer.py 0.00'],
): [env("IPP_PRINTER_MAKE_AND_MODEL") or b'h2g2bob\'s ipp-printer.py 0.00'],
(
SectionEnum.printer,
b'printer-state',
Expand Down Expand Up @@ -293,12 +301,12 @@ def printer_list_attributes(self):
SectionEnum.printer,
b'document-format-default',
TagEnum.mime_media_type
): [b'application/pdf'],
): self.ppd.document_format_default(),
(
SectionEnum.printer,
b'document-format-supported',
TagEnum.mime_media_type
): [b'application/pdf'],
): self.ppd.document_format_supported(),
(
SectionEnum.printer,
b'printer-is-accepting-jobs',
Expand All @@ -324,6 +332,13 @@ def printer_list_attributes(self):
b'compression-supported',
TagEnum.keyword
): [b'none'],
(
SectionEnum.printer,
b'printer-icons',
TagEnum.uri
): [b'http://' + self.uri + b'/printer-small.png',
b'http://' + self.uri + b'/printer.png',
b'http://' + self.uri + b'/printer-large.png'],
}
attr.update(self.minimal_attributes())
return attr
Expand Down Expand Up @@ -453,16 +468,19 @@ def operation_get_job_attributes_response(self, req, _psfile):


class SaveFilePrinter(StatelessPrinter):
def __init__(self, directory, filename_ext):
def __init__(self, uri, ppdfile, directory, filename_ext):
self.directory = directory
self.filename_ext = filename_ext

ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]
if ppdfile:
ppd = FilePPD(ppdfile)
else:
ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]

super(SaveFilePrinter, self).__init__(ppd=ppd)
super(SaveFilePrinter, self).__init__(uri=uri, ppd=ppd)

def handle_postscript(self, ipp_request, postscript_file):
filename = self.filename(ipp_request)
Expand All @@ -485,11 +503,11 @@ def leaf_filename(self, _ipp_request):


class SaveAndRunPrinter(SaveFilePrinter):
def __init__(self, directory, use_env, filename_ext, command):
def __init__(self, uri, ppdfile, directory, use_env, filename_ext, command):
self.command = command
self.use_env = use_env
super(SaveAndRunPrinter, self).__init__(
directory=directory, filename_ext=filename_ext
uri=uri, ppdfile=ppdfile, directory=directory, filename_ext=filename_ext
)

def run_after_saving(self, filename, ipp_request):
Expand All @@ -506,16 +524,19 @@ def run_after_saving(self, filename, ipp_request):


class RunCommandPrinter(StatelessPrinter):
def __init__(self, command, use_env, filename_ext):
def __init__(self, uri, ppdfile, command, use_env, filename_ext):
self.command = command
self.use_env = use_env

ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]
if ppdfile:
ppd = FilePPD(ppdfile)
else:
ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]

super(RunCommandPrinter, self).__init__(ppd=ppd)
super(RunCommandPrinter, self).__init__(uri=uri, ppd=ppd)

def handle_postscript(self, ipp_request, postscript_file):
logging.info('Running command for job')
Expand All @@ -534,16 +555,19 @@ def handle_postscript(self, ipp_request, postscript_file):


class PostageServicePrinter(StatelessPrinter):
def __init__(self, service_api, filename_ext):
def __init__(self, uri, ppdfile, service_api, filename_ext):
self.service_api = service_api
self.filename_ext = filename_ext

ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]
if ppdfile:
ppd = FilePPD(ppdfile)
else:
ppd = {
'ps': BasicPostscriptPPD(),
'pdf': BasicPdfPPD(),
}[filename_ext]

super(PostageServicePrinter, self).__init__(ppd=ppd)
super(PostageServicePrinter, self).__init__(uri=uri, ppd=ppd)

def handle_postscript(self, _ipp_request, postscript_file):
filename = b'ipp-server-{}.{}'.format(
Expand Down
Binary file added ippserver/data/printer-large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ippserver/data/printer-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ippserver/data/printer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions ippserver/ppd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,53 @@
from __future__ import absolute_import
from __future__ import print_function

from .server import local_file_location


class PPD(object):
def text(self):
raise NotImplementedError()

def document_format_default(self):
"""Scan the PPD for *cupsFilter2 and extract the 'best final' MIME-type"""
types = []
for line in self.text().split(b'\n'):
if line.startswith(b'*cupsFilter2'):
line = line.split(b':', maxsplit=1)[1].strip().lstrip(b'"').rstrip(b'"')
types.append(dict(zip(("src", "dst", "cost", "filter"), line.split())))
types = sorted(types, key=lambda type: type["cost"])
destination_types = [type["src"] for type in types]
if destination_types:
return [destination_types[0]]
else:
# assume PostScript
return [b'application/postscript']

def document_format_supported(self):
"""Scan the PPD for *cupsFilter2 and extract the 'supported' MIME-types"""
types = []
for line in self.text().split(b'\n'):
if line.startswith(b'*cupsFilter2'):
line = line.split(b':', maxsplit=1)[1].strip().lstrip(b'"').rstrip(b'"')
types.append(dict(zip(("src", "dst", "cost", "filter"), line.split())))
types = sorted(types, key=lambda type: type["cost"])
destination_types = [type["src"] for type in types]
if destination_types:
return destination_types
else:
# assume PostScript
return [b'application/postscript', b'application/octet-stream']


class FilePPD(PPD):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably make all the other PPD classes into normal files too - setup.py handles data_files, so this shouldn't be too bad. But I won't force you to do that as part of this PR!

def __init__(self, filename):
filepath = local_file_location(filename)
with open(filepath, "r") as ppdfile:
self.ppd = ppdfile.read()

def text(self):
return self.ppd.encode("ascii")


class BasicPostscriptPPD(PPD):
product = 'ipp-server'
Expand Down
9 changes: 9 additions & 0 deletions ippserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@


def local_file_location(filename):
filename = os.path.basename(filename)
if os.environ.get("IPP_DATA_DIR"):
return os.path.join(os.environ.get("IPP_DATA_DIR"), filename)
return os.path.join(os.path.dirname(__file__), 'data', filename)


Expand Down Expand Up @@ -115,6 +118,12 @@ def handle_www(self):
status=200, content_type='text/plain'
)
self.wfile.write(self.server.behaviour.ppd.text())
elif self.path.endswith('.png'):
self.send_headers(
status=200, content_type='image/png'
)
with open(local_file_location(self.path), 'rb') as wwwfile:
self.wfile.write(wwwfile.read())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do something here to prevent self.path being some horror like "../../etc/passwd". Assert that path is a regexp pattern, or something?

else:
self.send_headers(
status=404, content_type='text/plain'
Expand Down