def printer_print_api(): printer = hardware.Printer.query.get(1) pixels = image_encoding.default_pipeline( templating.default_template(flask.request.data)) hardware_message = messages.SetDeliveryAndPrint( device_address=printer.device_address, pixels=pixels, ) # If a printer is "offline" then we won't find the printer # connected and success will be false. success, next_print_id = protocol_loop.send_message( printer.device_address, hardware_message) if success: flask.flash('Sent your message to the printer!') stats.inc('printer.print.ok') else: flask.flash(("Could not send message because the " "printer {} is offline.").format(printer.name), 'error') stats.inc('printer.print.offline') # We know immediately if the printer wasn't online. if not success: model_message.failure_message = 'Printer offline' model_message.response_timestamp = datetime.datetime.utcnow() db.session.add(model_message) return 'ok'
def preview_api(): message = flask.request.data.decode('utf-8') pixels = image_encoding.default_pipeline( templating.default_template(message)) png = io.BytesIO() pixels.save(png, "PNG") stats.inc('printer.preview') return '<img style="width: 100%;" src="data:image/png;base64,{}">'.format(base64.b64encode(png.getvalue()))
def print_html(self, html, from_name, face='default'): from sirius.protocol import protocol_loop from sirius.protocol import messages from sirius.coding import image_encoding from sirius.coding import templating from sirius import stats from sirius.models import messages as model_messages pixels = image_encoding.default_pipeline( templating.default_template(html, from_name=from_name) ) hardware_message = None if face == "noface": hardware_message = messages.SetDeliveryAndPrintNoFace( device_address=self.device_address, pixels=pixels, ) else: hardware_message = messages.SetDeliveryAndPrint( device_address=self.device_address, pixels=pixels, ) # If a printer is "offline" then we won't find the printer # connected and success will be false. success, next_print_id = protocol_loop.send_message( self.device_address, hardware_message ) if success: stats.inc('printer.print.ok') else: stats.inc('printer.print.offline') # Store the same message in the database. png = io.BytesIO() pixels.save(png, "PNG") model_message = model_messages.Message( print_id=next_print_id, pixels=bytearray(png.getvalue()), sender_name=from_name, target_printer=self, ) # We know immediately if the printer wasn't online. if not success: model_message.failure_message = 'Printer offline' model_message.response_timestamp = datetime.datetime.utcnow() db.session.add(model_message) if not success: raise Printer.OfflineError()
def imageprint(printer_id): printer = hardware.Printer.query.get(printer_id) if printer is None: flask.abort(404) form = PrintForm() if flask.request.method == 'POST': pixels = image_encoding.image_pipeline(max(glob.iglob('/var/upload/*'), key=os.path.getctime)) ################## hardware_message = messages.SetDeliveryAndPrint( device_address=printer.device_address, pixels=pixels, ) # If a printer is "offline" then we won't find the printer # connected and success will be false. success, next_print_id = protocol_loop.send_message( printer.device_address, hardware_message) if success: flask.flash('Sent your message to the printer!') stats.inc('printer.print.ok') else: flask.flash(("Could not send message because the " "printer {} is offline.").format(printer.name), 'error') stats.inc('printer.print.offline') # Store the same message in the database. png = io.BytesIO() pixels.save(png, "PNG") model_message = model_messages.Message( print_id=next_print_id, pixels=bytearray(png.getvalue()), sender_id=login.current_user.id, target_printer=printer, ) # We know immediately if the printer wasn't online. if not success: model_message.failure_message = 'Printer offline' model_message.response_timestamp = datetime.datetime.utcnow() db.session.add(model_message) return flask.redirect(flask.url_for( 'printer_overview.printer_overview', printer_id=printer.id)) return flask.render_template( 'printer_print_file.html', printer=printer, form=form )
def preview(user_id, username, printer_id): assert user_id == login.current_user.id assert username == login.current_user.username message = flask.request.data.decode('utf-8') pixels = image_encoding.default_pipeline( templating.default_template(message, from_name=login.current_user.username)) png = io.BytesIO() pixels.save(png, "PNG") stats.inc('printer.preview') return '<img style="width: 100%;" src="data:image/png;base64,{}">'.format(base64.b64encode(png.getvalue()).decode('utf-8'))
def preview(user_id, username, printer_id): assert user_id == login.current_user.id assert username == login.current_user.username message = flask.request.data.decode('utf-8') pixels = image_encoding.default_pipeline( templating.default_template(message)) png = io.BytesIO() pixels.save(png, "PNG") stats.inc('printer.preview') return '<img style="width: 100%;" src="data:image/png;base64,{}">'.format(base64.b64encode(png.getvalue()))
def _decoder_loop(ws): """We run one decoder loop for every connected web socket. The first message is always PowerOn which contains a bridge_address. :param ws: A gevent websocket object. """ while True: raw_data = ws.receive() if raw_data is None: break # websocket closed by client try: data = json.loads(raw_data) except ValueError: stats.inc('json_decode_error.count') yield messages.MalformedEvent(raw_data, 'Could not decode json.') yield decoders.decode_message(data)
def _accept_step(x, bridge_state): """Handle a single decoded message. This is in its own function to simplify testing and to keep the if/elif indentation low. :param x: A type from sirius.protocol.message :param bridge_state: The BridgeState for this connection """ if type(x) == DeviceConnect: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) elif type(x) == DeviceDisconnect: bridge_state.mark_dead(x.device_address) elif type(x) == model_messages.BridgeLog: pass # TODO - write log to a place elif type(x) == EncryptionKeyRequired: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) claim_code = hardware.Printer.get_claim_code(x.device_address) if claim_code is None: # It's OK to have an unclaimed device, we just ignore it # for now. stats.inc('unclaimed.encryption_key_required.count') return add_key = AddDeviceEncryptionKey( bridge_address=x.bridge_address, device_address=x.device_address, claim_code=claim_code, ) send_message(x.device_address, add_key) elif type(x) == DeviceHeartbeat: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) elif type(x) == PowerOn: logging.info('Received superfluous PowerOn message. ' 'Probably backlog from previous socket error.') elif type(x) == DeviceDidPowerOn: # We don't really care about this one other than for debugging # maybe. bridge_state.mark_alive(x.device_address) elif type(x) == DeviceDidPrint: # TODO - This message is sent for two reasons: # 1) The user pressed the button and nothing was in the queue # "hello, I don't have anything to print [...]" # 2) We printed a queued job. # In case 2 we want to ack the print in the database. pass elif type(x) == BridgeCommandResponse: if x.command_id not in bridge_state.pending_commands: logger.error('Unexpected response (command_id not known) %r', x) return logger.debug('Got BridgeCommandResponse, ignoring.') elif type(x) == DeviceCommandResponse: if x.command_id not in bridge_state.pending_commands: logger.error('Unexpected response (command_id not known) %r', x) return logger.debug('Ack-ing message with print_id %s', x.command_id) model_messages.Message.ack(x.return_code, x.command_id) else: assert False, "Unexpected message {}".format(x)
def accept(ws): """Receiving loop for a single websocket. We're storing the websocket in a BridgeState class keyed on the bridge address. This is necessary to later find connected devices in `send_message`. :param ws: A gevent websocket object. """ loop = _decoder_loop(ws) power_on = loop.next() # stats & logging stats.inc('accepted.count') stats.inc('by_type.{}.count'.format(type(power_on).__name__)) logger.info("New connection from %r.", power_on) # Note that there is a potential race condition. If the bridge # responds to command_id 0 over a restart we may receive that # response just after we have sent out a command with id 0, but # it's the wrong response from an earlier command. bridge_state = BridgeState(websocket=ws, address=power_on.bridge_address) bridge_by_address[power_on.bridge_address] = bridge_state try: for message in loop: # stats & logging stats.inc('received.count') stats.inc('by_type.{}.count'.format(type(message).__name__)) logger.debug("Received %r.", message) _accept_step(message, bridge_state) # If the iterator closes normally then the client closed the # socket and we clean up normally. stats.inc('client_closed.count') except: stats.inc('protocol_loop_exception.count') logger.exception("Unexpected bridge error.") raise finally: logger.debug("Bridge disonnected: %r.", bridge_state) del bridge_by_address[power_on.bridge_address]
def printer_print(printer_id): printer = hardware.Printer.query.get(printer_id) if printer is None: flask.abort(404) # PERMISSIONS # the printer must either belong to this user, or be # owned by a friend if printer.owner.id == login.current_user.id: # fine pass elif printer.id in [p.id for p in login.current_user.friends_printers()]: # fine pass else: flask.abort(404) form = PrintForm() # Note that the form enforces access permissions: People can't # submit a valid printer-id that's not owned by the user or one of # the user's friends. choices = [(x.id, x.name) for x in login.current_user.printers] + [ (x.id, x.name) for x in login.current_user.friends_printers() ] form.target_printer.choices = choices # Set default printer on get if flask.request.method != 'POST': form.target_printer.data = printer.id if form.validate_on_submit(): # TODO: move image encoding into a pthread. # TODO: use templating to avoid injection attacks pixels = image_encoding.default_pipeline( templating.default_template(form.message.data)) hardware_message = messages.SetDeliveryAndPrint( device_address=printer.device_address, pixels=pixels, ) # If a printer is "offline" then we won't find the printer # connected and success will be false. success, next_print_id = protocol_loop.send_message( printer.device_address, hardware_message) if success: flask.flash('Sent your message to the printer!') stats.inc('printer.print.ok') else: flask.flash(("Could not send message because the " "printer {} is offline.").format(printer.name), 'error') stats.inc('printer.print.offline') # Store the same message in the database. png = io.BytesIO() pixels.save(png, "PNG") model_message = model_messages.Message( print_id=next_print_id, pixels=bytearray(png.getvalue()), sender_id=login.current_user.id, target_printer=printer, ) # We know immediately if the printer wasn't online. if not success: model_message.failure_message = 'Printer offline' model_message.response_timestamp = datetime.datetime.utcnow() db.session.add(model_message) return flask.redirect( flask.url_for('printer_overview.printer_overview', printer_id=printer.id)) return flask.render_template( 'printer_print.html', printer=printer, form=form, )
def printer_print(printer_id): printer = hardware.Printer.query.get(printer_id) if printer is None: flask.abort(404) # PERMISSIONS # the printer must either belong to this user, or be # owned by a friend if printer.owner.id == login.current_user.id: # fine pass elif printer.id in [p.id for p in login.current_user.friends_printers()]: # fine pass else: flask.abort(404) form = PrintForm() # Note that the form enforces access permissions: People can't # submit a valid printer-id that's not owned by the user or one of # the user's friends. choices = [ (x.id, x.name) for x in login.current_user.printers ] + [ (x.id, x.name) for x in login.current_user.friends_printers() ] form.target_printer.choices = choices # Set default printer on get if flask.request.method != 'POST': form.target_printer.data = printer.id if form.validate_on_submit(): # TODO: move image encoding into a pthread. # TODO: use templating to avoid injection attacks pixels = image_encoding.default_pipeline( templating.default_template(form.message.data)) hardware_message = messages.SetDeliveryAndPrint( device_address=printer.device_address, pixels=pixels, ) # If a printer is "offline" then we won't find the printer # connected and success will be false. success, next_print_id = protocol_loop.send_message( printer.device_address, hardware_message) if success: flask.flash('Sent your message to the printer!') stats.inc('printer.print.ok') else: flask.flash(("Could not send message because the " "printer {} is offline.").format(printer.name), 'error') stats.inc('printer.print.offline') # Store the same message in the database. png = io.BytesIO() pixels.save(png, "PNG") model_message = model_messages.Message( print_id=next_print_id, pixels=bytearray(png.getvalue()), sender_id=login.current_user.id, target_printer=printer, ) # We know immediately if the printer wasn't online. if not success: model_message.failure_message = 'Printer offline' model_message.response_timestamp = datetime.datetime.utcnow() db.session.add(model_message) return flask.redirect(flask.url_for( 'printer_overview.printer_overview', printer_id=printer.id)) return flask.render_template( 'printer_print.html', printer=printer, form=form, )
def _accept_step(x, bridge_state): """Handle a single decoded message. This is in its own function to simplify testing and to keep the if/elif indentation low. :param x: A type from sirius.protocol.message :param bridge_state: The BridgeState for this connection """ if type(x) == messages.DeviceConnect: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) elif type(x) == messages.DeviceDisconnect: bridge_state.mark_dead(x.device_address) elif type(x) == messages.BridgeLog: pass # TODO - write log to a place elif type(x) == messages.EncryptionKeyRequired: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) claim_code = hardware.Printer.get_claim_code(x.device_address) if claim_code is None: # It's OK to have an unclaimed device, we just ignore it # for now. stats.inc('unclaimed.encryption_key_required.count') return add_key = messages.AddDeviceEncryptionKey( bridge_address=x.bridge_address, device_address=x.device_address, claim_code=claim_code, ) send_message(x.device_address, add_key) elif type(x) == messages.DeviceHeartbeat: hardware.Printer.phone_home(x.device_address) bridge_state.mark_alive(x.device_address) elif type(x) == messages.PowerOn: logging.info('Received superfluous PowerOn message. ' 'Probably backlog from previous socket error.') elif type(x) == messages.DeviceDidPowerOn: # We don't really care about this one other than for debugging # maybe. bridge_state.mark_alive(x.device_address) elif type(x) == messages.DeviceDidPrint: # TODO - This message is sent for two reasons: # 1) The user pressed the button and nothing was in the queue # "hello, I don't have anything to print [...]" # 2) We printed a queued job. # In case 2 we want to ack the print in the database. pass elif type(x) == messages.BridgeCommandResponse: if x.command_id not in bridge_state.pending_commands: logger.error('Unexpected response (command_id not known) %r', x) return logger.debug('Got BridgeCommandResponse, ignoring.') elif type(x) == messages.DeviceCommandResponse: if x.command_id not in bridge_state.pending_commands: logger.error('Unexpected response (command_id not known) %r', x) return logger.debug('Ack-ing message with print_id %s', x.command_id) model_messages.Message.ack(x.return_code, x.command_id) else: assert False, "Unexpected message {}".format(x)
def accept(ws): """Receiving loop for a single websocket. We're storing the websocket in a BridgeState class keyed on the bridge address. This is necessary to later find connected devices in `send_message`. :param ws: A gevent websocket object. """ loop = _decoder_loop(ws) power_on = loop.next() # stats & logging stats.inc('accepted.count') stats.inc('by_type.{}.count'.format(type(power_on).__name__)) logger.info("New connection from %r.", power_on) # Note that there is a potential race condition. If the bridge # responds to command_id 0 over a restart we may receive that # response just after we have sent out a command with id 0, but # it's the wrong response from an earlier command. bridge_state = BridgeState(websocket=ws, address=power_on.bridge_address) bridge_by_address[power_on.bridge_address] = bridge_state try: for message in loop: # stats & logging stats.inc('received.count') stats.inc('by_type.{}.count'.format(type(message).__name__)) logger.debug("Received %r.", message) # make sure the bridge_state is still in the bridge_by_address dict. # (my theory is that the same bridge can lose a connection and make a # second connection before this loop realises the first connection # is gone. when the first one does finally error, it removes the # second's entry instead of its own.) bridge_by_address[power_on.bridge_address] = bridge_state _accept_step(message, bridge_state) # don't keep an idle session running inside this loop, # otherwise it uses a DB connection for every connected bridge db.session.remove() # If the iterator closes normally then the client closed the # socket and we clean up normally. stats.inc('client_closed.count') except: stats.inc('protocol_loop_exception.count') logger.exception("Unexpected bridge error.") raise finally: logger.debug("Bridge disonnected: %r.", bridge_state) db.session.remove() del bridge_by_address[power_on.bridge_address]