Skip to content
Merged
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
3 changes: 3 additions & 0 deletions pyipptool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
create_printer_subscription = wrapper. create_printer_subscription
cancel_job = wrapper.cancel_job
release_job = wrapper.release_job
create_job = wrapper.create_job
create_printer_subscription = wrapper.create_printer_subscription
cups_add_modify_class = wrapper.cups_add_modify_class
cups_add_modify_printer = wrapper.cups_add_modify_printer
Expand All @@ -26,7 +27,9 @@
get_subscriptions = wrapper.get_subscriptions
get_notifications = wrapper.get_notifications
pause_printer = wrapper.pause_printer
print_job = wrapper.print_job
resume_printer = wrapper.resume_printer
send_document = wrapper.send_document
hold_new_jobs = wrapper.hold_new_jobs
release_held_new_jobs = wrapper.release_held_new_jobs
cancel_subscription = wrapper.cancel_subscription
136 changes: 134 additions & 2 deletions pyipptool/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools
import os
import plistlib
import shutil
import subprocess
import tempfile
import time
Expand All @@ -11,6 +12,7 @@

from .forms import (cancel_job_form,
release_job_form,
create_job_form,
create_job_subscription_form,
create_printer_subscription_form,
cups_add_modify_class_form,
Expand All @@ -29,7 +31,9 @@
get_subscriptions_form,
get_notifications_form,
pause_printer_form,
print_job_form,
resume_printer_form,
send_document_form,
hold_new_jobs_form,
release_held_new_jobs_form,
cancel_subscription_form,
Expand Down Expand Up @@ -60,6 +64,7 @@ def pyipptool_coroutine(method):
will take care to consume the generator synchronously.
"""
method.ipptool_caller = True

@functools.wraps(method)
def sync_coroutine_consumer(*args, **kw):
gen = method(*args, **kw)
Expand All @@ -75,6 +80,54 @@ def sync_coroutine_consumer(*args, **kw):
return sync_coroutine_consumer


def _get_filename_for_content(content):
"""
Return the name of a file based on type of content
- already a file ?
- does he have a name ?
take its name
- else
copy to temp file and return its name
- binary content ?
copy to temp file and return its name

if a temp file is created the caller is responsible to
destroy the file. the flag delete is meant for it.
"""
file_ = None
delete = False
if isinstance(content, file):
# regular file
file_ = content
if isinstance(getattr(content, 'file', None), file):
# tempfile
file_ = content
if (hasattr(content, 'read') and hasattr(content, 'read') and
hasattr(content, 'tell')):
# most likely a file like object
file_ = content
if file_ is not None:
if file_.name:
name = file_.name
else:
with tempfile.NamedTemporaryFile(delete=False,
mode='rb') as tmp:
delete = True
shutil.copyfileobj(file_, tmp)
name = tmp.name
elif isinstance(content, basestring):
with tempfile.NamedTemporaryFile(delete=False, mode='w') as tmp:
delete = True
tmp.write(content)
name = tmp.name
else:
raise NotImplementedError(
'Got unknow document\'s content type {}'.format(
type(content)))

return name, delete


class MetaAsyncShifter(type):
"""
Based on async flage defined on IPPToolWrapper
Expand All @@ -94,7 +147,6 @@ def __new__(cls, name, bases, attrs):
return klass



class IPPToolWrapper(object):
__metaclass__ = MetaAsyncShifter
async = False
Expand Down Expand Up @@ -127,7 +179,6 @@ def timeout_handler(self, process, future):
break
time.sleep(.1)


def _call_ipptool(self, uri, request):
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
temp_file.write(request)
Expand Down Expand Up @@ -179,6 +230,49 @@ def cancel_job(self, uri,
response = yield self._call_ipptool(uri, request)
raise Return(response)

@pyipptool_coroutine
def create_job(self, uri,
printer_uri=None,
auth_info=colander.null,
job_billing=colander.null,
job_sheets=colander.null,
media=colander.null):
kw = {'header':
{'operation_attributes':
{'printer_uri': printer_uri}},
'auth_info': auth_info,
'job_billing': job_billing,
'job_sheets': job_sheets,
'media': media}
request = create_job_form.render(kw)
response = yield self._call_ipptool(uri, request)
raise Return(response)

@pyipptool_coroutine
def print_job(self, uri,
printer_uri=None,
auth_info=colander.null,
job_billing=colander.null,
job_sheets=colander.null,
media=colander.null,
document_content=None):
filename, delete = _get_filename_for_content(document_content)
kw = {'header':
{'operation_attributes':
{'printer_uri': printer_uri}},
'auth_info': auth_info,
'job_billing': job_billing,
'job_sheets': job_sheets,
'media': media,
'file': filename}
request = print_job_form.render(kw)
try:
response = yield self._call_ipptool(uri, request)
raise Return(response)
finally:
if delete:
os.unlink(filename)

@pyipptool_coroutine
def create_job_subscription(self,
uri,
Expand Down Expand Up @@ -588,6 +682,44 @@ def release_held_new_jobs(self, *args, **kw):
return self._hold_or_release_new_jobs(release_held_new_jobs_form,
*args, **kw)

@pyipptool_coroutine
def send_document(self, uri,
job_uri=colander.null,
printer_uri=colander.null,
job_id=colander.null,
requesting_user_name=None,
document_name=colander.null,
compression=colander.null,
document_format='application/pdf',
document_natural_language=colander.null,
last_document=True,
document_content=None,
):
"""
:param document_content: Binary Content or Named File
"""
delete = False
filename, delete = _get_filename_for_content(document_content)
kw = {'header':
{'operation_attributes':
{'job_uri': job_uri,
'printer_uri': printer_uri,
'job_id': job_id,
'requesting_user_name': requesting_user_name,
'document_name': document_name,
'compression': compression,
'document_format': document_format,
'document_natural_language': document_natural_language,
'last_document': last_document}},
'file': filename}
request = send_document_form.render(kw)
try:
response = yield self._call_ipptool(uri, request)
raise Return(response)
finally:
if delete:
os.unlink(filename)


class AsyncIPPToolWrapper(IPPToolWrapper):
async = True
Expand Down
6 changes: 6 additions & 0 deletions pyipptool/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .schemas import (cancel_job_schema,
release_job_schema,
create_job_schema,
create_job_subscription_schema,
create_printer_subscription_schema,
cups_add_modify_class_schema,
Expand All @@ -22,7 +23,9 @@
get_subscriptions_schema,
get_notifications_schema,
pause_printer_schema,
print_job_schema,
resume_printer_schema,
send_document_schema,
hold_new_jobs_schema,
release_held_new_jobs_schema,
cancel_subscription_schema,
Expand All @@ -36,6 +39,7 @@

cancel_job_form = deform.Form(cancel_job_schema)
release_job_form = deform.Form(release_job_schema)
create_job_form = deform.Form(create_job_schema)
create_job_subscription_form = deform.Form(
create_job_subscription_schema)
create_printer_subscription_form = deform.Form(
Expand All @@ -56,7 +60,9 @@
get_subscriptions_form = deform.Form(get_subscriptions_schema)
get_notifications_form = deform.Form(get_notifications_schema)
pause_printer_form = deform.Form(pause_printer_schema)
print_job_form = deform.Form(print_job_schema)
resume_printer_form = deform.Form(resume_printer_schema)
send_document_form = deform.Form(send_document_schema)
hold_new_jobs_form = deform.Form(hold_new_jobs_schema)
release_held_new_jobs_form = deform.Form(release_held_new_jobs_schema)
cancel_subscription_form = deform.Form(cancel_subscription_schema)
63 changes: 60 additions & 3 deletions pyipptool/schemas.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import colander
from .widgets import (IPPAttributeWidget, IPPBodyWidget, IPPGroupWidget,
IPPNameWidget, IPPTupleWidget, IPPConstantTupleWidget)
from .widgets import (IPPAttributeWidget, IPPBodyWidget, IPPFileWidget,
IPPGroupWidget, IPPNameWidget, IPPTupleWidget,
IPPConstantTupleWidget)


class IntegerOrTuple(colander.List):
Expand Down Expand Up @@ -40,6 +41,10 @@ class Language(colander.String):
pass


class MimeMediaType(colander.String):
pass


class Name(colander.String):
pass

Expand Down Expand Up @@ -235,6 +240,21 @@ class GetPrinterAttributesOperationAttributes(
widget=IPPAttributeWidget())


class SendDocumentOperationAttribute(JobOperationAttributes):
requesting_user_name = colander.SchemaNode(Name(),
widget=IPPAttributeWidget())
document_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
compression = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
document_format = colander.SchemaNode(MimeMediaType(),
widget=IPPAttributeWidget())
document_natural_language = colander.SchemaNode(
NaturalLanguage(),
widget=IPPAttributeWidget())
last_document = colander.SchemaNode(
colander.Boolean(false_val=0, true_val=1),
widget=IPPAttributeWidget())


class HeaderIPPSchema(colander.Schema):
name = colander.SchemaNode(colander.String(), widget=IPPNameWidget())
operation = colander.SchemaNode(colander.String(), widget=IPPNameWidget())
Expand Down Expand Up @@ -284,7 +304,8 @@ class BaseCupsAddModifyIPPSchema(BaseIPPSchema):
printer_info = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
printer_location = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
printer_more_info = colander.SchemaNode(Uri(), widget=IPPAttributeWidget())
printer_op_policy = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
printer_op_policy = colander.SchemaNode(Name(),
widget=IPPAttributeWidget())
printer_state = colander.SchemaNode(Enum(), widget=IPPAttributeWidget())
printer_state_message = colander.SchemaNode(
Text(),
Expand Down Expand Up @@ -380,6 +401,19 @@ class CupsRejectJobsSchema(BaseIPPSchema):
widget=IPPAttributeWidget())


class CreateJobSchema(BaseIPPSchema):
name = 'Create Job'
operation = 'Create-Job'
header = HeaderIPPSchema(widget=IPPConstantTupleWidget())
header['operation_attributes'] = OperationAttributesWithPrinterUri(
widget=IPPTupleWidget())
object_attributes_tag = 'job-attributes-tag'
auth_info = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
job_billing = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
job_sheets = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
media = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())


class CreateSubscriptionSchema(BaseIPPSchema):
header = HeaderIPPSchema(widget=IPPConstantTupleWidget())
header['operation_attributes'] = SubscriptionOperationAttributes(
Expand Down Expand Up @@ -470,6 +504,13 @@ class PausePrinterSchema(BaseIPPSchema):
object_attributes_tag = colander.null


class PrintJobSchema(CreateJobSchema):
name = 'Print Job'
operation = 'Print-Job'
object_attributes_tag = 'document-attributes-tag'
file = colander.SchemaNode(colander.String(), widget=IPPFileWidget())


class ResumePrinterSchema(PausePrinterSchema):
name = 'Resume Printer'
operation = 'Resume-Printer'
Expand All @@ -479,6 +520,16 @@ class ResumePrinterSchema(PausePrinterSchema):
object_attributes_tag = colander.null


class SendDocumentSchema(BaseIPPSchema):
name = 'Send Document'
operation = 'Send-Document'
header = HeaderIPPSchema(widget=IPPConstantTupleWidget())
header['operation_attributes'] = SendDocumentOperationAttribute(
widget=IPPTupleWidget())
object_attributes_tag = 'document-attributes-tag'
file = colander.SchemaNode(colander.String(), widget=IPPFileWidget())


class HoldNewJobsSchema(PausePrinterSchema):
name = 'Hold New Jobs'
operation = 'Hold-New-Jobs'
Expand Down Expand Up @@ -508,6 +559,8 @@ class CancelSubscriptionSchema(BaseIPPSchema):
create_job_subscription_schema = CreateJobSubscriptionSchema(
widget=IPPBodyWidget())

create_job_schema = CreateJobSchema(widget=IPPBodyWidget())

create_printer_subscription_schema = CreatePrinterSubscriptionSchema(
widget=IPPBodyWidget())

Expand Down Expand Up @@ -548,8 +601,12 @@ class CancelSubscriptionSchema(BaseIPPSchema):

pause_printer_schema = PausePrinterSchema(widget=IPPBodyWidget())

print_job_schema = PrintJobSchema(widget=IPPBodyWidget())

resume_printer_schema = ResumePrinterSchema(widget=IPPBodyWidget())

send_document_schema = SendDocumentSchema(widget=IPPBodyWidget())

hold_new_jobs_schema = HoldNewJobsSchema(widget=IPPBodyWidget())

release_held_new_jobs_schema = ReleaseHeldNewJobsSchema(widget=IPPBodyWidget())
Expand Down
8 changes: 8 additions & 0 deletions pyipptool/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ def serialize(self, field, cstruct=None, readonly=False):
return '{} "{}"'.format(name.upper(), value)


class IPPFileWidget(Widget):
def serialize(self, field, cstruct=None, readonly=False):
if not isinstance(cstruct, basestring):
raise ValueError('Wrong value provided for field {!r}'.format(
field.name))
return 'FILE {}'.format(cstruct)


class IPPGroupWidget(Widget):
def serialize(self, field, cstruct=None, readonly=False):
name = field.name
Expand Down
Binary file added tests/hello.pdf
Binary file not shown.
Loading