forked from Support/sp-hydra-veil-gui
update: fixed location page buttons display
This commit is contained in:
parent
5975b2d254
commit
5d2b6f2eac
11 changed files with 547 additions and 121 deletions
0
gui/v2/actions/disable_profiles.py
Normal file → Executable file
0
gui/v2/actions/disable_profiles.py
Normal file → Executable file
|
|
@ -1,35 +0,0 @@
|
||||||
def location_candidates(profile, preferred=None):
|
|
||||||
candidates = []
|
|
||||||
seen = set()
|
|
||||||
|
|
||||||
def add(val):
|
|
||||||
if val is None:
|
|
||||||
return
|
|
||||||
s = str(val).strip().lower().replace(' ', '_')
|
|
||||||
if s and s not in seen:
|
|
||||||
seen.add(s)
|
|
||||||
candidates.append(s)
|
|
||||||
|
|
||||||
if preferred:
|
|
||||||
add(preferred)
|
|
||||||
|
|
||||||
sources = []
|
|
||||||
try:
|
|
||||||
if profile and profile.connection and profile.connection.location:
|
|
||||||
sources.append(profile.connection.location)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if profile and profile.location:
|
|
||||||
sources.append(profile.location)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for source in sources:
|
|
||||||
add(getattr(source, 'id', None))
|
|
||||||
add(getattr(source, 'country_name', None))
|
|
||||||
add(getattr(source, 'name', None))
|
|
||||||
add(getattr(source, 'code', None))
|
|
||||||
add(getattr(source, 'country_code', None))
|
|
||||||
|
|
||||||
return candidates
|
|
||||||
74
gui/v2/actions/operation_result_dispatch.py
Executable file
74
gui/v2/actions/operation_result_dispatch.py
Executable file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import inspect
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from core.controllers.ConfigurationController import ConfigurationController
|
||||||
|
from core.models.Configuration import ConnectionChoice
|
||||||
|
from core.models.Result import ResultError
|
||||||
|
from core.services.prepare_tickets import ticket_tracker
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ENUM_MAP = {
|
||||||
|
ResultError.SUBSCRIPTION: (
|
||||||
|
ticket_tracker.wipe_one_ticket_sub,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
ResultError.CONNECTION: (
|
||||||
|
ConfigurationController.set_connection_enum,
|
||||||
|
{'connection_enum': ConnectionChoice.SYSTEM},
|
||||||
|
),
|
||||||
|
ResultError.INVALID_API_REPLY: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def has_function(enum_map: dict[Enum, Any], enum_key: Enum) -> bool:
|
||||||
|
action = enum_map.get(enum_key)
|
||||||
|
if action is None:
|
||||||
|
return False
|
||||||
|
function, _ = _normalize_action(action)
|
||||||
|
return callable(function)
|
||||||
|
|
||||||
|
|
||||||
|
def error_dispatch(enum_map: dict[Enum, Any], enum_key: Enum, **kwargs: Any) -> Any:
|
||||||
|
if enum_key not in enum_map:
|
||||||
|
return None
|
||||||
|
action = enum_map[enum_key]
|
||||||
|
if action is None:
|
||||||
|
return None
|
||||||
|
function, default_kwargs = _normalize_action(action)
|
||||||
|
call_kwargs = dict(default_kwargs)
|
||||||
|
call_kwargs.update(kwargs)
|
||||||
|
return function(**_filter_kwargs(function, call_kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def has_result_action(result, enum_map: dict[Enum, Any] | None = None) -> bool:
|
||||||
|
return has_function(enum_map or DEFAULT_ENUM_MAP, result.error_type)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_result_action(result, enum_map: dict[Enum, Any] | None = None, **kwargs: Any) -> Any:
|
||||||
|
return error_dispatch(enum_map or DEFAULT_ENUM_MAP, result.error_type, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_action(action: Any) -> tuple[Any, dict[str, Any]]:
|
||||||
|
if isinstance(action, tuple):
|
||||||
|
function = action[0]
|
||||||
|
if len(action) > 1 and isinstance(action[1], dict):
|
||||||
|
return function, action[1]
|
||||||
|
return function, {}
|
||||||
|
return action, {}
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_kwargs(function: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
signature = inspect.signature(function)
|
||||||
|
parameters = signature.parameters
|
||||||
|
if any(param.kind == inspect.Parameter.VAR_KEYWORD for param in parameters.values()):
|
||||||
|
return kwargs
|
||||||
|
accepted = {
|
||||||
|
name
|
||||||
|
for name, param in parameters.items()
|
||||||
|
if param.kind in (
|
||||||
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
inspect.Parameter.KEYWORD_ONLY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {key: value for key, value in kwargs.items() if key in accepted}
|
||||||
18
gui/v2/actions/operation_results.py
Executable file
18
gui/v2/actions/operation_results.py
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
|
||||||
|
|
||||||
|
def result_from_exception(exception: Exception, error_type: ResultError = ResultError.UNKNOWN) -> Result:
|
||||||
|
message = str(exception) or type(exception).__name__
|
||||||
|
return Result(valid=False, error_type=error_type, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
def result_from_payload(payload) -> Result:
|
||||||
|
if isinstance(payload, Result):
|
||||||
|
return payload
|
||||||
|
if isinstance(payload, Exception):
|
||||||
|
return result_from_exception(payload)
|
||||||
|
return Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.UNKNOWN,
|
||||||
|
message=str(payload) or 'The operation could not be completed.',
|
||||||
|
)
|
||||||
|
|
@ -26,5 +26,5 @@ def create_top_section(parent, css_path):
|
||||||
'marquee_timer': marquee_timer,
|
'marquee_timer': marquee_timer,
|
||||||
'text_start_x': 15,
|
'text_start_x': 15,
|
||||||
'text_end_x': 420,
|
'text_end_x': 420,
|
||||||
'text_width': 405,
|
'text_width': 500,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QApplication, QLabel, QPushButton, QButtonGroup
|
from PyQt6.QtWidgets import QApplication, QLabel, QPushButton, QButtonGroup, QScrollArea, QWidget
|
||||||
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont, QTransform
|
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont, QTransform
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
|
|
@ -20,6 +20,7 @@ class LocationPage(Page):
|
||||||
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
|
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
|
||||||
self.title.setGeometry(395, 40, 380, 40)
|
self.title.setGeometry(395, 40, 380, 40)
|
||||||
self.title.setText("Pick a location")
|
self.title.setText("Pick a location")
|
||||||
|
self._setup_scroll_area()
|
||||||
|
|
||||||
self.initial_display = QLabel(self)
|
self.initial_display = QLabel(self)
|
||||||
self.initial_display.setGeometry(5, 22, 355, 485)
|
self.initial_display.setGeometry(5, 22, 355, 485)
|
||||||
|
|
@ -70,13 +71,24 @@ class LocationPage(Page):
|
||||||
self.refresh_protocol_location_visibility()
|
self.refresh_protocol_location_visibility()
|
||||||
|
|
||||||
def create_interface_elements(self, available_locations):
|
def create_interface_elements(self, available_locations):
|
||||||
|
old_button_widget = self.scroll_area.takeWidget()
|
||||||
|
if old_button_widget is not None:
|
||||||
|
old_button_widget.deleteLater()
|
||||||
|
|
||||||
|
button_widget = QWidget()
|
||||||
|
button_widget.setStyleSheet("background: transparent;")
|
||||||
|
self._button_widget = button_widget
|
||||||
self.buttonGroup = QButtonGroup(self)
|
self.buttonGroup = QButtonGroup(self)
|
||||||
self.buttons = []
|
self.buttons = []
|
||||||
|
|
||||||
for j, (object_type, icon_name, geometry) in enumerate(available_locations):
|
for j, (object_type, icon_name, geometry) in enumerate(available_locations):
|
||||||
boton = object_type(self)
|
boton = object_type(button_widget)
|
||||||
boton.setGeometry(*geometry)
|
boton.setGeometry(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
geometry[2],
|
||||||
|
geometry[3],
|
||||||
|
)
|
||||||
boton.setIconSize(boton.size())
|
boton.setIconSize(boton.size())
|
||||||
boton.setCheckable(True)
|
boton.setCheckable(True)
|
||||||
|
|
||||||
|
|
@ -123,11 +135,61 @@ class LocationPage(Page):
|
||||||
boton.clicked.connect(
|
boton.clicked.connect(
|
||||||
lambda checked, loc=icon_name: self.show_location(loc))
|
lambda checked, loc=icon_name: self.show_location(loc))
|
||||||
|
|
||||||
|
content_height = max(
|
||||||
|
(geometry[3] for _, _, geometry in available_locations),
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
|
button_widget.setFixedSize(380, max(1, content_height))
|
||||||
|
self.scroll_area.setWidget(button_widget)
|
||||||
|
self._layout_visible_buttons()
|
||||||
|
|
||||||
|
def _setup_scroll_area(self):
|
||||||
|
self.scroll_area = QScrollArea(self)
|
||||||
|
self.scroll_area.setGeometry(405, 90, 385, 400)
|
||||||
|
self.scroll_area.setWidgetResizable(False)
|
||||||
|
self.scroll_area.setHorizontalScrollBarPolicy(
|
||||||
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
self.scroll_area.setStyleSheet("""
|
||||||
|
QScrollArea {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
QScrollBar:vertical {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
width: 10px;
|
||||||
|
margin: 0px 0px 0px 4px;
|
||||||
|
}
|
||||||
|
QScrollBar::handle:vertical {
|
||||||
|
background: #00ffff;
|
||||||
|
border-radius: 5px;
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 1px 0px;
|
||||||
|
}
|
||||||
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
def refresh_protocol_location_visibility(self):
|
def refresh_protocol_location_visibility(self):
|
||||||
for button in self.buttons:
|
for button in self.buttons:
|
||||||
location_key = getattr(button, 'location_icon_name', None)
|
location_key = getattr(button, 'location_icon_name', None)
|
||||||
locations = self.connection_manager.get_location_info(location_key)
|
locations = self.connection_manager.get_location_info(location_key)
|
||||||
button.setVisible(not locations or self._location_supports_selected_protocol(locations))
|
button.setVisible(not locations or self._location_supports_selected_protocol(locations))
|
||||||
|
self._layout_visible_buttons()
|
||||||
|
|
||||||
|
def _layout_visible_buttons(self):
|
||||||
|
visible_buttons = [button for button in self.buttons if not button.isHidden()]
|
||||||
|
for index, button in enumerate(visible_buttons):
|
||||||
|
button.move((index % 2) * 195, (index // 2) * 105)
|
||||||
|
|
||||||
|
if hasattr(self, '_button_widget'):
|
||||||
|
button_height = self.buttons[0].height() if self.buttons else 0
|
||||||
|
row_count = (len(visible_buttons) + 1) // 2
|
||||||
|
self._button_widget.setFixedSize(
|
||||||
|
380,
|
||||||
|
max(1, row_count * 105 - 30 + button_height),
|
||||||
|
)
|
||||||
|
|
||||||
def _current_protocol(self):
|
def _current_protocol(self):
|
||||||
profile_data = self.update_status.read_data()
|
profile_data = self.update_status.read_data()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ from core.models.system.SystemProfile import SystemProfile
|
||||||
|
|
||||||
from gui.v2.infrastructure.setup_observers import ticket_observer
|
from gui.v2.infrastructure.setup_observers import ticket_observer
|
||||||
from gui.v2.actions.database_health import GuiStorageDatabaseError
|
from gui.v2.actions.database_health import GuiStorageDatabaseError
|
||||||
|
from gui.v2.actions.operation_result_dispatch import has_result_action
|
||||||
|
from gui.v2.actions.operation_results import result_from_payload
|
||||||
from gui.v2.actions.profile_order import normalize_profile_order
|
from gui.v2.actions.profile_order import normalize_profile_order
|
||||||
from gui.v2.actions.profile_status import is_profile_enabled_for_gui
|
from gui.v2.actions.profile_status import is_profile_enabled_for_gui
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
|
|
@ -29,6 +31,7 @@ from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
|
||||||
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
|
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
|
||||||
from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup
|
from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup
|
||||||
from gui.v2.ui.popups.endpoint_verification_popup import EndpointVerificationPopup
|
from gui.v2.ui.popups.endpoint_verification_popup import EndpointVerificationPopup
|
||||||
|
from gui.v2.ui.popups.operation_result_popup import OperationResultPopup
|
||||||
from gui.v2.ui.popups.ticket_data_loss_popup import TicketDataLossPopup
|
from gui.v2.ui.popups.ticket_data_loss_popup import TicketDataLossPopup
|
||||||
from gui.v2.workers.worker import Worker
|
from gui.v2.workers.worker import Worker
|
||||||
from gui.v2.workers.worker_thread import WorkerThread
|
from gui.v2.workers.worker_thread import WorkerThread
|
||||||
|
|
@ -1257,10 +1260,43 @@ class MenuPage(Page):
|
||||||
self.worker.update_signal.connect(self.update_gui_main_thread)
|
self.worker.update_signal.connect(self.update_gui_main_thread)
|
||||||
self.worker.change_page.connect(self.change_app_page)
|
self.worker.change_page.connect(self.change_app_page)
|
||||||
self.worker.ticket_data_loss.connect(self.show_ticket_data_loss_popup)
|
self.worker.ticket_data_loss.connect(self.show_ticket_data_loss_popup)
|
||||||
|
self.worker.operation_failed.connect(self.handle_operation_failure)
|
||||||
|
|
||||||
thread = threading.Thread(target=self.worker.run)
|
thread = threading.Thread(target=self.worker.run)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
def handle_operation_failure(self, payload):
|
||||||
|
result = result_from_payload(payload)
|
||||||
|
if result.valid:
|
||||||
|
if result.message:
|
||||||
|
self.update_status.update_status(result.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
message = result.message or result.user_message()
|
||||||
|
self.update_status.update_status(message)
|
||||||
|
self.boton_just.setEnabled(True)
|
||||||
|
self.boton_just_session.setEnabled(True)
|
||||||
|
self.disconnect_button.setEnabled(True)
|
||||||
|
self.disconnect_system_wide_button.setEnabled(True)
|
||||||
|
|
||||||
|
is_actionable = has_result_action(result)
|
||||||
|
|
||||||
|
self.popup = OperationResultPopup(
|
||||||
|
self,
|
||||||
|
message=message,
|
||||||
|
action_button_text="Yes" if is_actionable else "OK",
|
||||||
|
cancel_button_text="No" if is_actionable else None,
|
||||||
|
action_result=is_actionable,
|
||||||
|
)
|
||||||
|
self.popup.action_selected.connect(
|
||||||
|
lambda accepted: self.handle_operation_popup_action(accepted))
|
||||||
|
self.popup.show()
|
||||||
|
|
||||||
|
def handle_operation_popup_action(self, accepted):
|
||||||
|
if self.worker is None:
|
||||||
|
return
|
||||||
|
self.worker.handle_operation_popup_choice(accepted)
|
||||||
|
|
||||||
def show_ticket_data_loss_popup(self, ticket_number, billing_code):
|
def show_ticket_data_loss_popup(self, ticket_number, billing_code):
|
||||||
self.custom_window.navigator.navigate("menu")
|
self.custom_window.navigator.navigate("menu")
|
||||||
self.update_status.update_status('Critical: invalid billing code from ticket.')
|
self.update_status.update_status('Critical: invalid billing code from ticket.')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem, QPushButton
|
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem, QPushButton
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
|
|
||||||
from core.controllers.ProfileController import ProfileController
|
|
||||||
from core.controllers.tickets.UseTicketController import get_unused_tickets
|
from core.controllers.tickets.UseTicketController import get_unused_tickets
|
||||||
|
|
||||||
from gui.v2.actions.locations import location_candidates
|
|
||||||
from gui.v2.infrastructure.setup_observers import ticket_observer
|
from gui.v2.infrastructure.setup_observers import ticket_observer
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
|
|
||||||
|
|
@ -91,20 +89,14 @@ class TicketOrBillingChoicePage(Page):
|
||||||
self.status_label.setText("Pick a ticket from the list first.")
|
self.status_label.setText("Pick a ticket from the list first.")
|
||||||
return
|
return
|
||||||
which_ticket = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
which_ticket = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||||
profile_id = self.update_status.current_profile_id
|
|
||||||
try:
|
try:
|
||||||
profile = ProfileController.get(int(profile_id))
|
profile_id = int(self.update_status.current_profile_id)
|
||||||
except Exception:
|
except (TypeError, ValueError):
|
||||||
profile = None
|
self.status_label.setText("Could not determine profile.")
|
||||||
candidates = location_candidates(profile)
|
|
||||||
if not candidates:
|
|
||||||
self.status_label.setText("Could not determine profile location.")
|
|
||||||
return
|
return
|
||||||
profile_data = {
|
profile_data = {
|
||||||
'id': int(profile_id),
|
'id': profile_id,
|
||||||
'use_ticket': which_ticket,
|
'use_ticket': which_ticket
|
||||||
'ticket_location': candidates[0],
|
|
||||||
'profile': profile
|
|
||||||
}
|
}
|
||||||
menu_page = self.custom_window.navigator.get_cached("menu")
|
menu_page = self.custom_window.navigator.get_cached("menu")
|
||||||
if menu_page:
|
if menu_page:
|
||||||
|
|
|
||||||
200
gui/v2/ui/popups/operation_result_popup.py
Executable file
200
gui/v2/ui/popups/operation_result_popup.py
Executable file
|
|
@ -0,0 +1,200 @@
|
||||||
|
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
||||||
|
from PyQt6.QtGui import QFont, QGuiApplication
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QDialog,
|
||||||
|
QFrame,
|
||||||
|
QHBoxLayout,
|
||||||
|
QLabel,
|
||||||
|
QPushButton,
|
||||||
|
QScrollArea,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
from gui.v2.ui.styles.styles import (
|
||||||
|
POPUP_ACTION_BUTTON_RED_QSS,
|
||||||
|
POPUP_BG_QSS,
|
||||||
|
POPUP_CANCEL_BUTTON_QSS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OperationResultPopup(QDialog):
|
||||||
|
action_selected = pyqtSignal(bool)
|
||||||
|
|
||||||
|
def __init__(self, parent=None, message="", title="Operation Failed", action_button_text="Yes", cancel_button_text="No", action_result=True):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent_window = parent
|
||||||
|
self.title_text = title or "Operation Failed"
|
||||||
|
self.message = message or "The operation could not be completed."
|
||||||
|
self.action_button_text = action_button_text
|
||||||
|
self.cancel_button_text = cancel_button_text
|
||||||
|
self.action_result = action_result
|
||||||
|
self._completed = False
|
||||||
|
self._use_parent_local_geometry = QGuiApplication.platformName().lower().startswith("wayland")
|
||||||
|
self.initUI()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setFixedSize(560, 300)
|
||||||
|
if self._use_parent_local_geometry:
|
||||||
|
if self.parent_window is not None:
|
||||||
|
self.setParent(self.parent_window)
|
||||||
|
self.setWindowFlags(Qt.WindowType.Widget)
|
||||||
|
else:
|
||||||
|
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
|
||||||
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
outer = QVBoxLayout(self)
|
||||||
|
outer.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
bg_widget = QWidget(self)
|
||||||
|
bg_widget.setStyleSheet(POPUP_BG_QSS)
|
||||||
|
outer.addWidget(bg_widget)
|
||||||
|
|
||||||
|
content_layout = QVBoxLayout(bg_widget)
|
||||||
|
content_layout.setContentsMargins(22, 14, 22, 18)
|
||||||
|
content_layout.setSpacing(12)
|
||||||
|
|
||||||
|
top_row = QHBoxLayout()
|
||||||
|
top_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
top_row.setSpacing(10)
|
||||||
|
|
||||||
|
title_label = QLabel(self.title_text)
|
||||||
|
title_label.setFont(QFont("Arial", 18, QFont.Weight.Bold))
|
||||||
|
title_label.setStyleSheet("color: #d62828; background: transparent; border: none;")
|
||||||
|
title_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
title_label.setMinimumWidth(0)
|
||||||
|
top_row.addWidget(title_label, 1)
|
||||||
|
|
||||||
|
close_button = QPushButton("X")
|
||||||
|
close_button.setFixedSize(36, 36)
|
||||||
|
close_button.setFont(QFont("Arial", 14, QFont.Weight.Bold))
|
||||||
|
close_button.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #444444;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
color: #d62828;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
close_button.clicked.connect(lambda: self._choose(False))
|
||||||
|
top_row.addWidget(close_button, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)
|
||||||
|
|
||||||
|
content_layout.addLayout(top_row)
|
||||||
|
|
||||||
|
scroll_area = QScrollArea()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||||
|
scroll_area.setStyleSheet("background: transparent; border: none;")
|
||||||
|
content_layout.addWidget(scroll_area, 1)
|
||||||
|
|
||||||
|
scroll_content = QWidget()
|
||||||
|
scroll_content.setStyleSheet("background: transparent; border: none;")
|
||||||
|
scroll_layout = QVBoxLayout(scroll_content)
|
||||||
|
scroll_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
message_label = QLabel(self.message)
|
||||||
|
message_label.setFont(QFont("Arial", 12))
|
||||||
|
message_label.setStyleSheet("color: #333333; background: transparent; border: none;")
|
||||||
|
message_label.setWordWrap(True)
|
||||||
|
message_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||||
|
message_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
||||||
|
scroll_layout.addWidget(message_label)
|
||||||
|
scroll_layout.addStretch()
|
||||||
|
scroll_area.setWidget(scroll_content)
|
||||||
|
|
||||||
|
button_row = QHBoxLayout()
|
||||||
|
button_row.addStretch()
|
||||||
|
|
||||||
|
if self.cancel_button_text is not None:
|
||||||
|
cancel_button = QPushButton(self.cancel_button_text)
|
||||||
|
cancel_button.setFixedSize(max(110, min(180, 22 + len(self.cancel_button_text) * 8)), 42)
|
||||||
|
cancel_button.setFont(QFont("Arial", 11, QFont.Weight.Bold))
|
||||||
|
cancel_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
|
cancel_button.setStyleSheet(POPUP_CANCEL_BUTTON_QSS)
|
||||||
|
cancel_button.clicked.connect(lambda: self._choose(False))
|
||||||
|
button_row.addWidget(cancel_button)
|
||||||
|
|
||||||
|
action_button = QPushButton(self.action_button_text)
|
||||||
|
action_button.setFixedSize(max(110, min(180, 22 + len(self.action_button_text) * 8)), 42)
|
||||||
|
action_button.setFont(QFont("Arial", 11, QFont.Weight.Bold))
|
||||||
|
action_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
|
action_button.setStyleSheet(POPUP_ACTION_BUTTON_RED_QSS)
|
||||||
|
action_button.clicked.connect(lambda: self._choose(self.action_result))
|
||||||
|
button_row.addWidget(action_button)
|
||||||
|
content_layout.addLayout(button_row)
|
||||||
|
|
||||||
|
if not self._use_parent_local_geometry:
|
||||||
|
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
|
||||||
|
def _choose(self, accepted):
|
||||||
|
if self._completed:
|
||||||
|
return
|
||||||
|
self._completed = True
|
||||||
|
self.action_selected.emit(accepted)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
if not self._completed:
|
||||||
|
self._completed = True
|
||||||
|
self.action_selected.emit(False)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self._center_on_parent()
|
||||||
|
super().show()
|
||||||
|
|
||||||
|
def showEvent(self, event):
|
||||||
|
super().showEvent(event)
|
||||||
|
self._center_on_parent()
|
||||||
|
QTimer.singleShot(0, self._center_on_parent)
|
||||||
|
|
||||||
|
def _center_on_parent(self):
|
||||||
|
parent = self.parent_window
|
||||||
|
if parent is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if not hasattr(parent, 'mapToGlobal') or not hasattr(parent, 'rect'):
|
||||||
|
return
|
||||||
|
parent_local_rect = parent.rect()
|
||||||
|
if parent_local_rect.isEmpty():
|
||||||
|
return
|
||||||
|
my_size = self.size()
|
||||||
|
if my_size.isEmpty() or my_size.width() <= 0 or my_size.height() <= 0:
|
||||||
|
my_size = self.sizeHint()
|
||||||
|
if self._use_parent_local_geometry:
|
||||||
|
target_x = parent_local_rect.x() + (parent_local_rect.width() - my_size.width()) // 2
|
||||||
|
target_y = parent_local_rect.y() + (parent_local_rect.height() - my_size.height()) // 2
|
||||||
|
target_x = max(parent_local_rect.left(), min(target_x, parent_local_rect.right() - my_size.width() + 1))
|
||||||
|
target_y = max(parent_local_rect.top(), min(target_y, parent_local_rect.bottom() - my_size.height() + 1))
|
||||||
|
else:
|
||||||
|
parent_center = parent.mapToGlobal(parent_local_rect.center())
|
||||||
|
target_x = parent_center.x() - my_size.width() // 2
|
||||||
|
target_y = parent_center.y() - my_size.height() // 2
|
||||||
|
screen = None
|
||||||
|
if hasattr(parent, 'screen'):
|
||||||
|
try:
|
||||||
|
screen = parent.screen()
|
||||||
|
except Exception:
|
||||||
|
screen = None
|
||||||
|
if screen is not None:
|
||||||
|
avail = screen.availableGeometry()
|
||||||
|
target_x = max(avail.left(), min(target_x, avail.right() - my_size.width()))
|
||||||
|
target_y = max(avail.top(), min(target_y, avail.bottom() - my_size.height()))
|
||||||
|
self.setGeometry(target_x, target_y, my_size.width(), my_size.height())
|
||||||
|
if self._use_parent_local_geometry:
|
||||||
|
self.raise_()
|
||||||
|
self.setFocus(Qt.FocusReason.PopupFocusReason)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
self.oldPos = event.globalPosition().toPoint()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
delta = event.globalPosition().toPoint() - self.oldPos
|
||||||
|
self.move(self.x() + delta.x(), self.y() + delta.y())
|
||||||
|
self.oldPos = event.globalPosition().toPoint()
|
||||||
|
|
@ -7,7 +7,6 @@ from core.controllers.tickets.FailedVerificationController import (
|
||||||
evaluate_if_its_the_key,
|
evaluate_if_its_the_key,
|
||||||
prepare_tickets_with_saved_blind_sigs,
|
prepare_tickets_with_saved_blind_sigs,
|
||||||
)
|
)
|
||||||
from core.controllers.tickets.UseTicketController import use_ticket
|
|
||||||
|
|
||||||
from gui.v2.infrastructure.setup_observers import (
|
from gui.v2.infrastructure.setup_observers import (
|
||||||
connection_observer,
|
connection_observer,
|
||||||
|
|
@ -22,7 +21,6 @@ class TicketingWorkerThread(QThread):
|
||||||
not_paid = pyqtSignal()
|
not_paid = pyqtSignal()
|
||||||
paid_check_failed = pyqtSignal(str)
|
paid_check_failed = pyqtSignal(str)
|
||||||
prep_done = pyqtSignal(object)
|
prep_done = pyqtSignal(object)
|
||||||
use_done = pyqtSignal(object)
|
|
||||||
failed_verification_evaluated = pyqtSignal(object)
|
failed_verification_evaluated = pyqtSignal(object)
|
||||||
saved_blind_prep_done = pyqtSignal(object)
|
saved_blind_prep_done = pyqtSignal(object)
|
||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
|
|
@ -71,14 +69,5 @@ class TicketingWorkerThread(QThread):
|
||||||
elif self.action == 'PREPARE_SAVED_BLIND_SIGS':
|
elif self.action == 'PREPARE_SAVED_BLIND_SIGS':
|
||||||
result = prepare_tickets_with_saved_blind_sigs(ticket_observer, connection_observer)
|
result = prepare_tickets_with_saved_blind_sigs(ticket_observer, connection_observer)
|
||||||
self.saved_blind_prep_done.emit(result)
|
self.saved_blind_prep_done.emit(result)
|
||||||
elif self.action == 'USE_TICKET':
|
|
||||||
result = use_ticket(
|
|
||||||
which_ticket=self.params['which_ticket'],
|
|
||||||
which_location=self.params['which_location'],
|
|
||||||
ticket_observer=ticket_observer,
|
|
||||||
connection_observer=connection_observer,
|
|
||||||
profile=self.params['profile']
|
|
||||||
)
|
|
||||||
self.use_done.emit(result)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.error.emit(str(e))
|
self.error.emit(str(e))
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ from core.Errors import (
|
||||||
UnsupportedApplicationVersionError,
|
UnsupportedApplicationVersionError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.v2.actions.locations import location_candidates
|
|
||||||
from gui.v2.actions.database_health import GuiStorageDatabaseError
|
from gui.v2.actions.database_health import GuiStorageDatabaseError
|
||||||
|
from gui.v2.actions.operation_result_dispatch import dispatch_result_action
|
||||||
|
from gui.v2.actions.operation_results import result_from_exception
|
||||||
from gui.v2.infrastructure.screen_size import get_max_screensize
|
from gui.v2.infrastructure.screen_size import get_max_screensize
|
||||||
from gui.v2.infrastructure.setup_observers import (
|
from gui.v2.infrastructure.setup_observers import (
|
||||||
application_version_observer,
|
application_version_observer,
|
||||||
|
|
@ -39,6 +40,7 @@ class Worker(QObject):
|
||||||
update_signal = pyqtSignal(str, bool, int, int, str)
|
update_signal = pyqtSignal(str, bool, int, int, str)
|
||||||
change_page = pyqtSignal(str, bool)
|
change_page = pyqtSignal(str, bool)
|
||||||
ticket_data_loss = pyqtSignal(str, str)
|
ticket_data_loss = pyqtSignal(str, str)
|
||||||
|
operation_failed = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, profile_data):
|
def __init__(self, profile_data):
|
||||||
self.profile_data = profile_data
|
self.profile_data = profile_data
|
||||||
|
|
@ -50,6 +52,8 @@ class Worker(QObject):
|
||||||
self.profile_type = None
|
self.profile_type = None
|
||||||
self._ticket_error_emitted = False
|
self._ticket_error_emitted = False
|
||||||
self._consumed_ticket = None
|
self._consumed_ticket = None
|
||||||
|
self._pending_operation_result = None
|
||||||
|
self._pending_operation_kwargs = {}
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -70,8 +74,7 @@ class Worker(QObject):
|
||||||
|
|
||||||
if 'use_ticket' in self.profile_data:
|
if 'use_ticket' in self.profile_data:
|
||||||
ticket_billing_code = self._consume_ticket(
|
ticket_billing_code = self._consume_ticket(
|
||||||
self.profile_data['use_ticket'],
|
self.profile_data['use_ticket'])
|
||||||
self.profile_data.get('ticket_location'))
|
|
||||||
if ticket_billing_code is None:
|
if ticket_billing_code is None:
|
||||||
return
|
return
|
||||||
self.profile_data['billing_code'] = ticket_billing_code
|
self.profile_data['billing_code'] = ticket_billing_code
|
||||||
|
|
@ -84,18 +87,20 @@ class Worker(QObject):
|
||||||
self.profile_data['billing_code'] = ticket_billing_code
|
self.profile_data['billing_code'] = ticket_billing_code
|
||||||
|
|
||||||
if 'billing_code' in self.profile_data:
|
if 'billing_code' in self.profile_data:
|
||||||
subscription = SubscriptionController.get(
|
try:
|
||||||
self.profile_data['billing_code'], connection_observer=connection_observer)
|
subscription = SubscriptionController.get(
|
||||||
|
self.profile_data['billing_code'], connection_observer=connection_observer)
|
||||||
|
except Exception as e:
|
||||||
|
if self._consumed_ticket is not None:
|
||||||
|
self._emit_subscription_lookup_failure(exception=e)
|
||||||
|
return
|
||||||
|
subscription = None
|
||||||
if subscription is not None:
|
if subscription is not None:
|
||||||
ProfileController.attach_subscription(
|
ProfileController.attach_subscription(
|
||||||
self.profile, subscription)
|
self.profile, subscription)
|
||||||
else:
|
else:
|
||||||
if self._consumed_ticket is not None:
|
if self._consumed_ticket is not None:
|
||||||
self._ticket_error_emitted = True
|
self._emit_subscription_lookup_failure()
|
||||||
self.ticket_data_loss.emit(
|
|
||||||
self._consumed_ticket,
|
|
||||||
str(self.profile_data.get('billing_code', '')),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
self.change_page.emit('The billing code is invalid.', True)
|
self.change_page.emit('The billing code is invalid.', True)
|
||||||
return
|
return
|
||||||
|
|
@ -108,15 +113,21 @@ class Worker(QObject):
|
||||||
ignore_exceptions.append(ProfileStateConflictError)
|
ignore_exceptions.append(ProfileStateConflictError)
|
||||||
ignore_tuple = tuple(ignore_exceptions)
|
ignore_tuple = tuple(ignore_exceptions)
|
||||||
max_resolution = get_max_screensize()
|
max_resolution = get_max_screensize()
|
||||||
ProfileController.enable(self.profile, ignore=ignore_tuple, profile_observer=profile_observer,
|
enable_result = ProfileController.enable(self.profile, ignore=ignore_tuple, profile_observer=profile_observer,
|
||||||
application_version_observer=application_version_observer,
|
application_version_observer=application_version_observer,
|
||||||
connection_observer=connection_observer, ticket_observer=ticket_observer, max_resolution=max_resolution)
|
connection_observer=connection_observer, ticket_observer=ticket_observer, max_resolution=max_resolution)
|
||||||
|
if isinstance(enable_result, Result) and not enable_result.valid:
|
||||||
|
self._emit_operation_failure(enable_result)
|
||||||
|
return
|
||||||
except EndpointVerificationError:
|
except EndpointVerificationError:
|
||||||
self.update_signal.emit(
|
self.update_signal.emit(
|
||||||
"ENDPOINT_VERIFICATION_ERROR", False, self.profile_data['id'], None, None)
|
"ENDPOINT_VERIFICATION_ERROR", False, self.profile_data['id'], None, None)
|
||||||
except (InvalidSubscriptionError, MissingSubscriptionError) as e:
|
except (InvalidSubscriptionError, MissingSubscriptionError) as e:
|
||||||
self.change_page.emit(
|
if self._consumed_ticket is not None:
|
||||||
f"Subscription missing or invalid for profile {self.profile_data['id']}", True)
|
self._emit_subscription_lookup_failure(exception=e)
|
||||||
|
else:
|
||||||
|
self.change_page.emit(
|
||||||
|
f"Subscription missing or invalid for profile {self.profile_data['id']}", True)
|
||||||
except ProfileActivationError:
|
except ProfileActivationError:
|
||||||
self.update_signal.emit(
|
self.update_signal.emit(
|
||||||
"The profile could not be enabled", False, None, None, None)
|
"The profile could not be enabled", False, None, None, None)
|
||||||
|
|
@ -170,9 +181,6 @@ class Worker(QObject):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _location_candidates(self, preferred=None):
|
|
||||||
return location_candidates(self.profile, preferred)
|
|
||||||
|
|
||||||
def _profile_has_valid_subscription(self):
|
def _profile_has_valid_subscription(self):
|
||||||
try:
|
try:
|
||||||
sub = getattr(self.profile, 'subscription', None)
|
sub = getattr(self.profile, 'subscription', None)
|
||||||
|
|
@ -201,55 +209,137 @@ class Worker(QObject):
|
||||||
try:
|
try:
|
||||||
print("using a random ticket from GUI...")
|
print("using a random ticket from GUI...")
|
||||||
which_ticket, error_msg = do_we_use_a_random_ticket(ticket_observer)
|
which_ticket, error_msg = do_we_use_a_random_ticket(ticket_observer)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
self._emit_ticket_failure(None, exception=e)
|
||||||
return None
|
return None
|
||||||
if error_msg:
|
if error_msg:
|
||||||
self._ticket_error_emitted = True
|
self._emit_ticket_failure(
|
||||||
self.change_page.emit(f'Ticket use failed: {error_msg}', True)
|
None,
|
||||||
|
result=Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.TICKET,
|
||||||
|
message=f'Ticket use failed: {error_msg}',
|
||||||
|
),
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
if which_ticket is None or which_ticket == 'error':
|
if which_ticket is None or which_ticket == 'error':
|
||||||
return None
|
return None
|
||||||
return self._consume_ticket(which_ticket, None)
|
return self._consume_ticket(which_ticket)
|
||||||
|
|
||||||
def _consume_ticket(self, which_ticket, which_location):
|
def _ticket_location_id(self):
|
||||||
candidates = self._location_candidates(preferred=which_location)
|
location = getattr(self.profile, 'location', None)
|
||||||
if not candidates:
|
location_id = getattr(location, 'id', None)
|
||||||
self._ticket_error_emitted = True
|
if location_id is None:
|
||||||
self.change_page.emit('Could not determine profile location for ticket use.', True)
|
return None
|
||||||
|
if isinstance(location_id, str):
|
||||||
|
location_id = location_id.strip()
|
||||||
|
if location_id == '':
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(location_id)
|
||||||
|
except (TypeError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
last_msg = None
|
def _emit_ticket_failure(self, which_ticket, result=None, message=None, exception=None):
|
||||||
for cand in candidates:
|
|
||||||
try:
|
|
||||||
outcome = use_ticket(
|
|
||||||
which_ticket=which_ticket,
|
|
||||||
which_location=cand,
|
|
||||||
ticket_observer=ticket_observer,
|
|
||||||
connection_observer=connection_observer,
|
|
||||||
profile=self.profile
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
last_msg = str(e)
|
|
||||||
continue
|
|
||||||
if not isinstance(outcome, dict):
|
|
||||||
last_msg = 'invalid_response'
|
|
||||||
continue
|
|
||||||
billing_code = outcome.get('billing_code')
|
|
||||||
if outcome.get('valid') and billing_code:
|
|
||||||
self._consumed_ticket = str(which_ticket)
|
|
||||||
return billing_code
|
|
||||||
if billing_code:
|
|
||||||
self._consumed_ticket = str(which_ticket)
|
|
||||||
return billing_code
|
|
||||||
msg = outcome.get('message', 'failed')
|
|
||||||
last_msg = msg
|
|
||||||
if msg != 'invalid_location':
|
|
||||||
self._ticket_error_emitted = True
|
|
||||||
self.change_page.emit(f'Ticket use failed: {msg}', True)
|
|
||||||
return None
|
|
||||||
|
|
||||||
self._ticket_error_emitted = True
|
self._ticket_error_emitted = True
|
||||||
self.change_page.emit(f'Ticket use failed: {last_msg or "no valid location"}', True)
|
if exception is not None:
|
||||||
|
result = result_from_exception(exception)
|
||||||
|
elif result is None:
|
||||||
|
result = Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.UNKNOWN,
|
||||||
|
message=message or 'Ticket use failed.',
|
||||||
|
)
|
||||||
|
self._emit_operation_failure(result, which_ticket=which_ticket)
|
||||||
|
|
||||||
|
def _emit_operation_failure(self, result, **kwargs):
|
||||||
|
self._pending_operation_result = result
|
||||||
|
self._pending_operation_kwargs = {
|
||||||
|
key: value for key, value in kwargs.items()
|
||||||
|
if value is not None
|
||||||
|
}
|
||||||
|
self.operation_failed.emit(result)
|
||||||
|
|
||||||
|
def handle_operation_popup_choice(self, accepted):
|
||||||
|
if not accepted:
|
||||||
|
return None
|
||||||
|
if not isinstance(self._pending_operation_result, Result):
|
||||||
|
return None
|
||||||
|
return dispatch_result_action(
|
||||||
|
self._pending_operation_result,
|
||||||
|
**self._pending_operation_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _emit_subscription_lookup_failure(self, exception=None):
|
||||||
|
message = (
|
||||||
|
"Ticket was accepted, but the subscription details could not be "
|
||||||
|
"retrieved. Check your connection and try again."
|
||||||
|
)
|
||||||
|
if exception is not None:
|
||||||
|
error_text = str(exception)
|
||||||
|
if error_text:
|
||||||
|
message = f"{message} {error_text}"
|
||||||
|
self._emit_ticket_failure(
|
||||||
|
self._consumed_ticket,
|
||||||
|
result=Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.CONNECTION,
|
||||||
|
message=message,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _consume_ticket(self, which_ticket):
|
||||||
|
which_location = self._ticket_location_id()
|
||||||
|
if which_location is None:
|
||||||
|
self._emit_ticket_failure(
|
||||||
|
which_ticket,
|
||||||
|
result=Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.MISSING_DATA,
|
||||||
|
message='Could not determine profile location for ticket use.',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
outcome = use_ticket(
|
||||||
|
which_ticket=which_ticket,
|
||||||
|
which_location=which_location,
|
||||||
|
ticket_observer=ticket_observer,
|
||||||
|
connection_observer=connection_observer,
|
||||||
|
profile=self.profile
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self._emit_ticket_failure(which_ticket, exception=e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not isinstance(outcome, Result):
|
||||||
|
self._emit_ticket_failure(
|
||||||
|
which_ticket,
|
||||||
|
result=Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.INVALID_API_REPLY,
|
||||||
|
message=f'use_ticket returned {type(outcome).__name__}, expected Result.',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if outcome.valid:
|
||||||
|
billing_code = outcome.data
|
||||||
|
if not billing_code:
|
||||||
|
self._emit_ticket_failure(
|
||||||
|
which_ticket,
|
||||||
|
result=Result(
|
||||||
|
valid=False,
|
||||||
|
error_type=ResultError.INVALID_API_REPLY,
|
||||||
|
message='Ticket use succeeded but no billing code was returned.',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
self._consumed_ticket = str(which_ticket)
|
||||||
|
return billing_code
|
||||||
|
|
||||||
|
self._emit_ticket_failure(which_ticket, result=outcome)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def handle_profile_status(self, profile, is_enabled):
|
def handle_profile_status(self, profile, is_enabled):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue