Exemple #1
0
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'
Exemple #2
0
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()))
Exemple #3
0
    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()
Exemple #4
0
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
    )
Exemple #5
0
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'))
Exemple #6
0
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()))
Exemple #7
0
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)
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #10
0
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]
Exemple #11
0
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,
    )
Exemple #12
0
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,
    )
Exemple #13
0
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)
Exemple #14
0
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]
Exemple #15
0
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]