PK!mezprinting/__init__.py__version__ = '0.2.0' from ezprinting.print_job import PrintJob from ezprinting.print_server import PrintServer from ezprinting.printer import Printer PK!^C\\ezprinting/print_job.py# -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals import json from .printer import Printer from .print_server import PrintServer, GCP_BASE_URI STATE0_DRAFT = "DRAFT" STATE1_HELD = "HELD" STATE2_QUEUED = "QUEUED" STATE3_IN_PROGRESS = "IN_PROGRESS" STATE4_STOPPED = "STOPPED" STATE5_DONE = "DONE" STATE6_ABORTED = "ABORTED" GENERIC_TITLE = "A print job" DEFAULT_CONTENT_TYPE = 'application/pdf' class PrintJob: def __init__(self, printer: Printer, content, content_type: str = DEFAULT_CONTENT_TYPE, title: str = GENERIC_TITLE, options=None): self.printer = printer self.print_server = self.printer.print_server self.content = content self.content_type = content_type self.title = title self.job_id = None self.submitted = False self.gcp_submit_response = None self.gcp_submit_response_content = None self.gcp_last_message = "" self.state = STATE0_DRAFT if not options: if self.print_server.is_gcp: options = {"version": "1.0", "print": {}} elif self.print_server.is_cups: options = {} self.options = options def print(self): conn = self.print_server.open_connection() if self.print_server.is_cups: self.job_id = conn.createJob(self.printer.id, self.title, self.options) conn.startDocument(self.printer.id, self.job_id, self.title, self.content_type, 1) # Requires pycups >= 1.9.74, lower versions were buggy status = conn.writeRequestData(self.content, len(self.content)) if (conn.finishDocument(self.printer.id)==0): self.submitted = True self.status = STATE2_QUEUED if status==100 else STATE0_DRAFT return self.submitted elif self.print_server.is_gcp: # Must NOT set content-type as part of payload below payload = \ {"printerid": self.printer.id, \ "title": self.title, \ "ticket": json.dumps(self.options) } # Here, the second 'content' could be anything, it is there because # requests library requires a 'filename' but we need a filename without # extension to not mess up content-type files = {'content': ('content', self.content, self.content_type)} response = conn.post('{}/submit'.format(GCP_BASE_URI), data=payload, files=files) if response.status_code == 200: self.submitted = True self.gcp_submit_response = response self.gcp_submit_response_content = json.loads(response.content, strict=False) self.gcp_last_success = self.gcp_submit_response_content["success"] if self.gcp_last_success: self.job_id = self.gcp_submit_response_content["job"]["id"] self.state = self.gcp_submit_response_content["job"]["semanticState"]["state"]["type"] self.gcp_last_message = self.gcp_submit_response_content["message"] else: pass return self.gcp_last_success @classmethod def new_cups(cls, printer_name, content, content_type: str = DEFAULT_CONTENT_TYPE, host: str="localhost:631", username: str="", password: str="", title: str=GENERIC_TITLE, options=None): ps = PrintServer.cups(host=host, username=username, password=password) p = Printer(print_server=ps, name_or_id=printer_name) pj = cls(printer=p, content=content, content_type=content_type, title=title, options=options) pj.print() return pj @classmethod def new_gcp(cls, service_account: str, printer_id: str, content, content_type: str = DEFAULT_CONTENT_TYPE, title: str = GENERIC_TITLE, ticket=None): ps = PrintServer.gcp(name="GCP", service_account=service_account) p = Printer(ps, printer_id) pj = cls(printer=p, content=content, content_type=content_type, title=title, options=ticket) pj.print() return pj PK!XYf f ezprinting/print_server.py# -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals import cups import json from google.oauth2 import service_account from google.auth.transport.requests import AuthorizedSession SERVER_TYPE_CUPS = "CUPS" SERVER_TYPE_GOOGLE_CLOUD_PRINT = "GCP" PRINT_SERVER_GENERIC_NAME="A Print Server" DEFAULT_CUPS_HOST="localhost:631" GCP_SCOPEs = ['https://www.googleapis.com/auth/cloudprint'] GCP_BASE_URI = 'https://www.google.com/cloudprint' class PrintServer: def __init__(self, name, server_type, params_dict): self._name = name self._type = server_type self.is_cups = False self.is_gcp = False if (self._type == SERVER_TYPE_CUPS): self.is_cups = True self._cups_host = params_dict["cups_host"] self._cups_user = params_dict["cups_username"] self._cups_passwd = params_dict["cups_password"] elif (self._type == SERVER_TYPE_GOOGLE_CLOUD_PRINT): self.is_gcp = True self._service_account_json = params_dict else: raise ValueError("Invalid server_type.") @classmethod def cups(cls, host=DEFAULT_CUPS_HOST, username="", password="", name=PRINT_SERVER_GENERIC_NAME): d = {} d["cups_host"] = host d["cups_username"] = username d["cups_password"] = password d["name"] = name return cls(name=name, server_type=SERVER_TYPE_CUPS, params_dict=d) @classmethod def gcp(cls, service_account: str, name: str=PRINT_SERVER_GENERIC_NAME): return cls(name=name, server_type=SERVER_TYPE_GOOGLE_CLOUD_PRINT, params_dict=json.loads(service_account, strict=False)) def test_connection(self): success = True error_message = "Connection OK!" if self.is_cups: try: conn = self.open_connection() except RuntimeError as e: success = False error_message = str(e) elif self.is_gcp: try: session = self.open_connection() r = session.get("{}{}".format(GCP_BASE_URI, '/search?use_cdd=true&connection_status=ALL')) except Exception as e: success = False error_message = str(e) else: if not r.ok: success = False error_message = r.reason session.close() return success, error_message def open_connection(self): if (self.is_cups): cups.setServer(self._cups_host) cups.setUser(self._cups_user) cups.setPasswordCB(lambda a: self._cups_passwd) return cups.Connection() elif (self.is_gcp): credentials = service_account.Credentials.from_service_account_info(self._service_account_json, scopes=GCP_SCOPEs) return AuthorizedSession(credentials) else: raise RuntimeError("Server type error!") def get_printers(self): if self.is_cups: conn = self.open_connection() return conn.getPrinters() elif self.is_gcp: session = self.open_connection() r = session.get(GCP_BASE_URI + '/search?use_cdd=true&extra_fields=connectionStatus&q=&type=&connection_status=ALL') j = json.loads(r.content, strict=False) return j['printers'] PK!1p.ezprinting/printer.py# -*- coding: utf-8 -*- from __future__ import division, print_function, unicode_literals import json import cups from ezprinting.print_server import PrintServer, GCP_BASE_URI class Printer: def __init__(self, print_server: PrintServer, name_or_id: str): self.print_server = print_server self.id = name_or_id def check_printer_exists(self, conn=None): if not conn: conn = self.print_server.open_connection() if self.print_server.is_cups: try: p = self.get_printer_attributes(conn=conn) except cups.IPPError: return False return True if p['printer-name'] == self.id else False elif self.print_server.is_gcp: p = self.get_printer_attributes(conn=conn) if not p['success']: self.enable_printer(conn=conn) p = self.get_printer_attributes(conn=conn) return p['success'] def get_printer_attributes(self, conn=None): if not conn: conn = self.print_server.open_connection() if self.print_server.is_cups: return conn.getPrinterAttributes(self.id) elif self.print_server.is_gcp: r = conn.get('{}/printer?printerid={}'.format(GCP_BASE_URI, self.id)) j = json.loads(r.content, strict=False) return j def enable_printer(self, conn=None): if not conn: conn = self.print_server.open_connection() if self.print_server.is_cups: conn.enablePrinter(self.id) elif self.print_server.is_gcp: conn.get('{}/processinvite?accept=true&printerid={}'.format(GCP_BASE_URI, self.id)) PK!ﰰ"ezprinting-0.2.5.dist-info/LICENSECopyright 2018 Marcelo Bello Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H WX ezprinting-0.2.5.dist-info/WHEEL A н#Z."jm)Afb~ڠO68oF04UhoAf f4=4h0k::wXPK!HI3&#ezprinting-0.2.5.dist-info/METADATAZms_q>ԐPڑQJēd#p$/p MwPrδ{}gF2*M1GPJ.Qq,i\V+SR&kT6BI[?U%S,T*0EzR\|}#6e*ʘeEfT\8jRbXuiKLoqb|̜v}UX6ZJT, /̓ǽҎ5JTfK$Ȥz]t*\uouesl ^bِӦ㇎?}'?}g3>S5Rvrm;QlrGvCv ?yШKeJ52araꢞJVS:5"<׵O_%zuq-B[!Vғ|EbNNit"p''bɊ⹘CDp*J)V) Xf-#ɄXܮrda4bL Gmy;_-[дOerg,e& Z6eiZCi[]M F +#1::6:YZbW *䍩&IgUN/˼̔2ӔlPb<ŀJ\4Y9OQɲѝiEiLg8bq (i/D=Lg <O)U1c [3hL(PuώQk=zc畱ـSK]uN?y4eo4JaR<?r,?Mxd̃{Oᢎ B`?3|%G'&UZїr&d`3NYKzLtMk>]n71e=ZV$+j9ȒcWr- N80yYx{ۡCBo 9ЎgksuLeh$PU^q@CB_]!<>:-7S5齳 fG#ѱ~piWoͣu?t_ԟ .?w>#?+m)aGD0>n^k0Kdq0?W{W鴻b5f~Pq*N^,d+v2,k\),7T-B9t5'iTBy aV9I:ٴ bO ߌHjS:0c5%o%qz\l[UX-HM"U^l{[:"ؒBhve J_uK}9DÉΰٿ609mM,Ki.ny!+>؆{U-_ݼ})WCޏX,ԯ!ۖ RBH?Ws) ]i6GmQ.h~4[TَsmydkGn:#>Q2*G"8(Jo\[\qmIPP@gw^d%|~iK8wFKzSaڕ-me0)"Ҍ%ʽxç%Wy:'/]8:A@X'}R"nt-Yr* c };|kƃhSd5/sf_bdf!-YsQ!ThFcbR,PNf8u-p+ⴑo9 Hl1*t 25TbHUtO.\n|RsRN똻$aN׋ Qo5D+2H@$;R[ p#Y)x BM1ĝO2w`8,pm\ȉ1kuyC^H$O7: 7b06540eBopq;7Zo^"k0@nH ce?Yӗ`]b=V@U5}P Fda>UW>7Na2*+t4i R'P\;W四K߽?)gmmTdqgA˽/烻cQFG)."DGU;ZQ;OuXXD윍on=K+{+[1HלN23} @}?rm]k[|@$TVwk: Y _wSWڡ]]>-c`X#ŠƟ>aa$ 7r{HwB5]6?VQ5F?@yL]r9n.EjJtnS!?PҭcTiC ړ®H7Jov*:l)J5뱝G.+~Ǭ YDcOⴋܱL=ݮv%}M@r?4dn~m?#Jj뚃?moGt4 #.||@;4 [+ 0֋ʠf/Vsy%O8Tw6jJy@t.sENtF+T f[u/޹[q+N -UԢq%Cr>:zrs̢0 Mu3ӻΕ?'-tekf;9H)W'18EQ!ʾ*6Ag/jDAѡ ͣ:sO*- #@ڵ ŨwNG BY(]-|!~kVUS{,lؑ^}!DoPK!H3q!ezprinting-0.2.5.dist-info/RECORD}n@}eYNEAn"n&PE@P$'1Ƥ-~t}ьER4HȪPC`:zU8eCOhuƸ;'A)䉃K5QHV6ޛ,{JǏ!_d4pc̅B@OetFq G|j 29 M6cB\{G,KBAt9v.v4OåŌ3iS*-;w,d>i WpM־5A&B^ڣi[V#)7&/.X(F:;8V%9,w 5-wKN}uC  um5 ?TS@g~"qElmo_PK!mezprinting/__init__.pyPK!^C\\ezprinting/print_job.pyPK!XYf f ]ezprinting/print_server.pyPK!1p.ezprinting/printer.pyPK!ﰰ"%ezprinting-0.2.5.dist-info/LICENSEPK!H WX >*ezprinting-0.2.5.dist-info/WHEELPK!HI3&#*ezprinting-0.2.5.dist-info/METADATAPK!H3q!(:ezprinting-0.2.5.dist-info/RECORDPKR;