def process_virus_scan_passed(self, filename):
    reference = get_reference_from_filename(filename)
    notification = dao_get_notification_by_reference(reference)
    current_app.logger.info('notification id {} Virus scan passed: {}'.format(
        notification.id, filename))

    is_test_key = notification.key_type == KEY_TYPE_TEST

    scan_pdf_object = s3.get_s3_object(
        current_app.config['LETTERS_SCAN_BUCKET_NAME'], filename)
    old_pdf = scan_pdf_object.get()['Body'].read()

    sanitise_response, result = _sanitise_precompiled_pdf(
        self, notification, old_pdf)
    new_pdf = None
    if result == 'validation_passed':
        new_pdf = base64.b64decode(sanitise_response["file"].encode())

        redaction_failed_message = sanitise_response.get(
            "redaction_failed_message")
        if redaction_failed_message and not is_test_key:
            current_app.logger.info('{} for notification id {} ({})'.format(
                redaction_failed_message, notification.id, filename))
            copy_redaction_failed_pdf(filename)

    billable_units = get_billable_units_for_letter_page_count(
        sanitise_response.get("page_count"))

    # TODO: Remove this once CYSP update their template to not cross over the margins
    if notification.service_id == UUID(
            'fe44178f-3b45-4625-9f85-2264a36dd9ec'):  # CYSP
        # Check your state pension submit letters with good addresses and notify tags, so just use their supplied pdf
        new_pdf = old_pdf

    if result == 'validation_failed' and not new_pdf:
        current_app.logger.info(
            'Invalid precompiled pdf received {} ({})'.format(
                notification.id, filename))
        _move_invalid_letter_and_update_status(
            notification=notification,
            filename=filename,
            scan_pdf_object=scan_pdf_object,
            message=sanitise_response["message"],
            invalid_pages=sanitise_response.get("invalid_pages"),
            page_count=sanitise_response.get("page_count"))
        return

    current_app.logger.info(
        'notification id {} ({}) sanitised and ready to send'.format(
            notification.id, filename))

    try:
        _upload_pdf_to_test_or_live_pdf_bucket(
            new_pdf,
            filename,
            is_test_letter=is_test_key,
            created_at=notification.created_at)

        update_letter_pdf_status(reference=reference,
                                 status=NOTIFICATION_DELIVERED
                                 if is_test_key else NOTIFICATION_CREATED,
                                 billable_units=billable_units)
        scan_pdf_object.delete()
    except BotoClientError:
        current_app.logger.exception(
            "Error uploading letter to live pdf bucket for notification: {}".
            format(notification.id))
        update_notification_status_by_id(notification.id,
                                         NOTIFICATION_TECHNICAL_FAILURE)
def process_sanitised_letter(self, sanitise_data):
    letter_details = encryption.decrypt(sanitise_data)

    filename = letter_details['filename']
    notification_id = letter_details['notification_id']

    current_app.logger.info('Processing sanitised letter with id {}'.format(notification_id))
    notification = get_notification_by_id(notification_id, _raise=True)

    if notification.status != NOTIFICATION_PENDING_VIRUS_CHECK:
        current_app.logger.info(
            'process-sanitised-letter task called for notification {} which is in {} state'.format(
                notification.id, notification.status)
        )
        return

    try:
        original_pdf_object = s3.get_s3_object(current_app.config['LETTERS_SCAN_BUCKET_NAME'], filename)

        if letter_details['validation_status'] == 'failed':
            current_app.logger.info('Processing invalid precompiled pdf with id {} (file {})'.format(
                notification_id, filename))

            _move_invalid_letter_and_update_status(
                notification=notification,
                filename=filename,
                scan_pdf_object=original_pdf_object,
                message=letter_details['message'],
                invalid_pages=letter_details['invalid_pages'],
                page_count=letter_details['page_count'],
            )
            return

        current_app.logger.info('Processing valid precompiled pdf with id {} (file {})'.format(
            notification_id, filename))

        billable_units = get_billable_units_for_letter_page_count(letter_details['page_count'])
        is_test_key = notification.key_type == KEY_TYPE_TEST

        # Updating the notification needs to happen before the file is moved. This is so that if updating the
        # notification fails, the task can retry because the file is in the same place.
        update_letter_pdf_status(
            reference=notification.reference,
            status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED,
            billable_units=billable_units,
            recipient_address=letter_details['address']
        )

        # The original filename could be wrong because we didn't know the postage.
        # Now we know if the letter is international, we can check what the filename should be.
        upload_file_name = get_letter_pdf_filename(
            reference=notification.reference,
            crown=notification.service.crown,
            created_at=notification.created_at,
            ignore_folder=True,
            postage=notification.postage
        )

        move_sanitised_letter_to_test_or_live_pdf_bucket(
            filename,
            is_test_key,
            notification.created_at,
            upload_file_name,
        )
        # We've moved the sanitised PDF from the sanitise bucket, but still need to delete the original file:
        original_pdf_object.delete()

    except BotoClientError:
        # Boto exceptions are likely to be caused by the file(s) being in the wrong place, so retrying won't help -
        # we'll need to manually investigate
        current_app.logger.exception(
            f"Boto error when processing sanitised letter for notification {notification.id} (file {filename})"
        )
        update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
        raise NotificationTechnicalFailureException
    except Exception:
        try:
            current_app.logger.exception(
                "RETRY: calling process_sanitised_letter task for notification {} failed".format(notification.id)
            )
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. " \
                      "The task process_sanitised_letter failed for notification {}. " \
                      "Notification has been updated to technical-failure".format(notification.id)
            update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
def process_virus_scan_passed(self, filename):
    reference = get_reference_from_filename(filename)
    notification = dao_get_notification_by_reference(reference)
    current_app.logger.info("notification id {} Virus scan passed: {}".format(
        notification.id, filename))

    is_test_key = notification.key_type == KEY_TYPE_TEST

    scan_pdf_object = s3.get_s3_object(
        current_app.config["LETTERS_SCAN_BUCKET_NAME"], filename)
    old_pdf = scan_pdf_object.get()["Body"].read()

    try:
        billable_units = get_page_count(old_pdf)
    except PdfReadError:
        current_app.logger.exception(
            msg="Invalid PDF received for notification_id: {}".format(
                notification.id))
        _move_invalid_letter_and_update_status(notification, filename,
                                               scan_pdf_object)
        return

    sanitise_response = _sanitise_precompiled_pdf(self, notification, old_pdf)
    if not sanitise_response:
        new_pdf = None
    else:
        sanitise_response = sanitise_response.json()
        try:
            new_pdf = base64.b64decode(sanitise_response["file"].encode())
        except JSONDecodeError:
            new_pdf = sanitise_response.content

        redaction_failed_message = sanitise_response.get(
            "redaction_failed_message")
        if redaction_failed_message and not is_test_key:
            current_app.logger.info("{} for notification id {} ({})".format(
                redaction_failed_message, notification.id, filename))
            copy_redaction_failed_pdf(filename)

    # TODO: Remove this once CYSP update their template to not cross over the margins
    if notification.service_id == UUID(
            "fe44178f-3b45-4625-9f85-2264a36dd9ec"):  # CYSP
        # Check your state pension submit letters with good addresses and notify tags, so just use their supplied pdf
        new_pdf = old_pdf

    if not new_pdf:
        current_app.logger.info(
            "Invalid precompiled pdf received {} ({})".format(
                notification.id, filename))
        _move_invalid_letter_and_update_status(notification, filename,
                                               scan_pdf_object)
        return
    else:
        current_app.logger.info(
            "Validation was successful for precompiled pdf {} ({})".format(
                notification.id, filename))

    current_app.logger.info(
        "notification id {} ({}) sanitised and ready to send".format(
            notification.id, filename))

    try:
        _upload_pdf_to_test_or_live_pdf_bucket(new_pdf,
                                               filename,
                                               is_test_letter=is_test_key)

        update_letter_pdf_status(
            reference=reference,
            status=NOTIFICATION_DELIVERED
            if is_test_key else NOTIFICATION_CREATED,
            billable_units=billable_units,
        )
        scan_pdf_object.delete()
    except BotoClientError:
        current_app.logger.exception(
            "Error uploading letter to live pdf bucket for notification: {}".
            format(notification.id))
        update_notification_status_by_id(notification.id,
                                         NOTIFICATION_TECHNICAL_FAILURE)