Large changes to the flow for using tickets. More formalized updating of JSON with explicit tools, instead of generic JSON functions. Orchestration returns Return objects, which are then formatted to dict for the GUI.
This commit is contained in:
parent
fcf469bd03
commit
44558fb514
6 changed files with 117 additions and 116 deletions
|
|
@ -1,5 +1,15 @@
|
||||||
# Major Change Log:
|
# Major Change Log:
|
||||||
|
|
||||||
|
# Use Ticket Orchestration
|
||||||
|
### Aug 23, 2026
|
||||||
|
Large changes to the flow for using tickets. More formalized updating of JSON with explicit tools, instead of generic JSON functions. Orchestration returns Return objects, which are then formatted to dict for the GUI.
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
# Manual WG Enable
|
||||||
|
### Aug 23, 2026
|
||||||
|
Manual Explicit Wireguard enable via nmcli for Improved Cross-Linux distro support.
|
||||||
|
<br/>
|
||||||
|
|
||||||
# Cython Introduced
|
# Cython Introduced
|
||||||
### Aug 22, 2026
|
### Aug 22, 2026
|
||||||
Cython for Miller's Loop Cryptography first introduced, which dramatically reduces verification times. Includes a setup.py option for manual builds if desired. README for manual compile if desired. And a fallback import using the legacy python-only system.
|
Cython for Miller's Loop Cryptography first introduced, which dramatically reduces verification times. Includes a setup.py option for manual builds if desired. README for manual compile if desired. And a fallback import using the legacy python-only system.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ if TYPE_CHECKING:
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
# from core.observers.BaseObserver import BaseObserver
|
# from core.observers.BaseObserver import BaseObserver
|
||||||
from core.services.using_tickets.use_ticket_orchestrator import use_ticket_orchestrator
|
from core.services.using_tickets.use_ticket_orchestrator import use_ticket_orchestrator
|
||||||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse
|
|
||||||
|
|
||||||
from core.services.prepare_tickets.ticket_tracker import does_ticket_tracker_exist
|
from core.services.prepare_tickets.ticket_tracker import does_ticket_tracker_exist
|
||||||
|
|
||||||
|
|
@ -144,10 +143,10 @@ use_ticket function requires:
|
||||||
|
|
||||||
def use_ticket(
|
def use_ticket(
|
||||||
which_ticket: int,
|
which_ticket: int,
|
||||||
which_location: str,
|
which_location: int, # Location.id
|
||||||
ticket_observer: TicketObserver,
|
ticket_observer: TicketObserver,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
profile: Optional[Union[SessionProfile, SystemProfile]] = None,
|
profile: Optional[Union[SessionProfile, SystemProfile]] = None, # only for assassin or single profiles.
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
||||||
which_ticket = str(which_ticket) # type: ignore
|
which_ticket = str(which_ticket) # type: ignore
|
||||||
|
|
@ -168,24 +167,14 @@ def use_ticket(
|
||||||
|
|
||||||
# the actual work here, everything else is just handling:
|
# the actual work here, everything else is just handling:
|
||||||
ticket_observer.notify("connecting", "Connecting..")
|
ticket_observer.notify("connecting", "Connecting..")
|
||||||
reply = use_ticket_orchestrator(which_ticket, which_location, connection_observer)
|
reply_object = use_ticket_orchestrator(which_ticket, which_location, connection_observer)
|
||||||
|
|
||||||
# API reply did NOT go through:
|
# TRANSLATE FOR UI
|
||||||
if isinstance(reply, ApiResponse):
|
if reply_object.valid:
|
||||||
error_msg = reply.message
|
return {"valid": True, "billing_code": reply_object.data}
|
||||||
reply_as_dict = {"valid": False, "message": f"API Failed: {error_msg}"}
|
|
||||||
return reply_as_dict
|
|
||||||
|
|
||||||
# this is dict in theory,
|
error_msg = reply_object.message
|
||||||
if isinstance(reply, dict):
|
return {"valid": False, "message": error_msg}
|
||||||
valid = reply.get("valid", False)
|
|
||||||
# then if it worked, update the profile to save the ticket,
|
|
||||||
if valid and profile:
|
|
||||||
profile.ticket = which_ticket
|
|
||||||
profile.save()
|
|
||||||
logger.info(f"Saved ticket {which_ticket} to profile {profile.id}!")
|
|
||||||
|
|
||||||
return reply
|
|
||||||
|
|
||||||
|
|
||||||
def pick_a_random_ticket(ticket_observer: TicketObserver) -> dict:
|
def pick_a_random_ticket(ticket_observer: TicketObserver) -> dict:
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ class Location(BaseModel):
|
||||||
return {
|
return {
|
||||||
"country_code": self.country_code,
|
"country_code": self.country_code,
|
||||||
"code": self.code,
|
"code": self.code,
|
||||||
"time_zone": self.time_zone
|
"time_zone": self.time_zone,
|
||||||
|
"location_id": self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,34 +169,32 @@ def find_ticket_by_sub(target_subscription: str) -> int | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# This is currently not used, but it could be. Useful tool for explicitly changing subvalues.
|
def update_ticket_tracker(which_ticket: str, status: str, location=None, subscription=None) -> bool:
|
||||||
def change_ticket(which_ticket: str, status: str, location=None, subscription=None) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Purpose:
|
Purpose:
|
||||||
Update a specific which_ticket entry in the JSON.
|
Update a specific which_ticket entry in the JSON.
|
||||||
Rank:
|
Rank:
|
||||||
Cross-Module Helper
|
Cross-Module Helper
|
||||||
Args:
|
|
||||||
which_ticket: The key number (e.g., "1", "2")
|
|
||||||
status: New status ("unused" or "used")
|
|
||||||
location: Value for location field (default None)
|
|
||||||
subscription: Value for subscription field (default None)
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
|
does NOT raise errors
|
||||||
|
Called by:
|
||||||
|
use_ticket_orchestrator
|
||||||
"""
|
"""
|
||||||
# Force string, to avoid duplicate keys:
|
# Force string, to avoid duplicate keys:
|
||||||
which_ticket = str(which_ticket)
|
which_ticket = str(which_ticket)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = read_entire_json(Constants.TICKET_TRACKER_PATH)
|
data = read_entire_json(Constants.TICKET_TRACKER_PATH)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError, IsADirectoryError) as e:
|
||||||
data = {}
|
logger.error(f"Error with reading the ticket tracker: {str(e)}. Returning False")
|
||||||
|
return False
|
||||||
|
|
||||||
logger.info(f"DEBUG: Keys in file after read: {list(data.keys())}")
|
logger.info(f"DEBUG: Keys in file after read: {list(data.keys())}")
|
||||||
logger.info(f"DEBUG: Looking for which_ticket: '{which_ticket}' (type: {type(which_ticket).__name__})")
|
logger.info(f"DEBUG: Looking for which_ticket: '{which_ticket}' (type: {type(which_ticket).__name__})")
|
||||||
logger.info(f"DEBUG: Full data structure: {json.dumps(data, indent=2)}")
|
# logger.info(f"DEBUG: Full data structure: {json.dumps(data, indent=2)}")
|
||||||
|
|
||||||
# STEP 1: Check if which_ticket exists (and should exist if file was read correctly)
|
# STEP 1: Check if which_ticket exists
|
||||||
if which_ticket not in data:
|
if which_ticket not in data:
|
||||||
logger.warning(f"⚠️ which_ticket '{which_ticket}' NOT FOUND. Available keys: {list(data.keys())}")
|
logger.warning(f"⚠️ which_ticket '{which_ticket}' NOT FOUND. Available keys: {list(data.keys())}")
|
||||||
logger.warning(f"Creating new entry for '{which_ticket}'")
|
logger.warning(f"Creating new entry for '{which_ticket}'")
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import traceback
|
||||||
# this first gathers the right data, then sends it for validation,
|
# this first gathers the right data, then sends it for validation,
|
||||||
def send_unblinded_ticket_to_server(
|
def send_unblinded_ticket_to_server(
|
||||||
which_ticket: int,
|
which_ticket: int,
|
||||||
which_location: str,
|
which_location: int,
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
) -> dict|ApiResponse:
|
) -> dict|ApiResponse:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,121 +5,124 @@ if TYPE_CHECKING:
|
||||||
from essentials.observers.ConnectionObserver import ConnectionObserver
|
from essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
##############
|
##############
|
||||||
from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server
|
from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server
|
||||||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
|
|
||||||
|
|
||||||
from core.utils.basic_operations.write_or_read_from_json import (
|
|
||||||
update_value_in_json_with_two_values
|
|
||||||
)
|
|
||||||
from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
|
from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
|
||||||
from core.errors.exceptions import *
|
from core.services.prepare_tickets.ticket_tracker import update_ticket_tracker
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
|
|
||||||
import traceback
|
CUSTOMER_SERVICE_MSG = "Please speak with customer service."
|
||||||
|
|
||||||
# prep the ticket tracker:
|
|
||||||
billing_folder = Constants.HV_TICKETING_CONFIG_HOME
|
|
||||||
ticket_tracker_path = f"{billing_folder}/ticket_tracker.json"
|
|
||||||
|
|
||||||
# Coordinates using the ticket anonymously.
|
|
||||||
# To do so, it sends to the server & updates the 'ticker_tracker' JSON with new data:
|
|
||||||
def use_ticket_orchestrator(
|
def use_ticket_orchestrator(
|
||||||
which_ticket: int,
|
which_ticket: int,
|
||||||
which_location: str,
|
which_location: int, # Location.id
|
||||||
connection_observer: ConnectionObserver,
|
connection_observer: ConnectionObserver,
|
||||||
) -> dict | ApiResponse:
|
) -> Result:
|
||||||
|
"""
|
||||||
|
Purpose:
|
||||||
|
Coordinates using a ticket.
|
||||||
|
Rank:
|
||||||
|
Orchestrator
|
||||||
|
Method:
|
||||||
|
1) Send to the server
|
||||||
|
2) Evaluate the response
|
||||||
|
3) updates the 'ticker_tracker' JSON with new data
|
||||||
|
4) Formats error messages for UI
|
||||||
|
Called by:
|
||||||
|
UseTicketController
|
||||||
|
Returns:
|
||||||
|
Return Objects
|
||||||
|
do NOT raise errors
|
||||||
|
"""
|
||||||
|
|
||||||
# send to server:
|
###################################
|
||||||
|
# SEND TO THE SERVER
|
||||||
|
###################################
|
||||||
api_reply_object = send_unblinded_ticket_to_server(
|
api_reply_object = send_unblinded_ticket_to_server(
|
||||||
which_ticket, which_location, connection_observer
|
which_ticket, which_location, connection_observer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# NETWORK ERROR.
|
||||||
if not api_reply_object.valid:
|
if not api_reply_object.valid:
|
||||||
return api_reply_object
|
return Result(valid=False, error_type=ResultError.CONNECTION, message=f"Could not establish a connection to Ticketing Server. {api_reply_object.message}.")
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# VERIFY DATA STRUCTURE
|
||||||
|
###################################
|
||||||
raw_data_dict = api_reply_object.data
|
raw_data_dict = api_reply_object.data
|
||||||
|
|
||||||
# logger.debug(f"[USE TICKET ORCHESTRATOR] We have received a raw dictionary of {raw_data_dict}")
|
# INVALID STRUCTURE:
|
||||||
|
|
||||||
if not isinstance(raw_data_dict, dict):
|
if not isinstance(raw_data_dict, dict):
|
||||||
logger.debug(f"[USE TICKET ORCHESTRATOR] Invalid Data format returned from the API server.")
|
error_msg = f"The Ticketing Server gave an invalid data format {type(raw_data_dict)} in it's reply."
|
||||||
return {"valid": False, "message": "invalid_format"}
|
logger.error(f"{error_msg} with raw value of {raw_data_dict}.")
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
if "valid" not in raw_data_dict:
|
if "valid" not in raw_data_dict:
|
||||||
logger.debug(f"[USE TICKET ORCHESTRATOR] Invalid Data format returned from the API server.")
|
error_msg = "The Ticketing Server gave an invalid data format that won't even tell us if it's valid or not."
|
||||||
return {"valid": False, "message": "invalid_format"}
|
logger.error(f"{error_msg} with a raw value of {raw_data_dict}")
|
||||||
is_it_valid = raw_data_dict.get("valid")
|
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=f"{error_msg} {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
if "billing_code" in raw_data_dict:
|
logger.debug("If we made it to this point, the API server connected and gave a correctly structured reply.")
|
||||||
billing_code = raw_data_dict.get("billing_code", None)
|
|
||||||
|
|
||||||
"""
|
###################################
|
||||||
the reason this 'billing_code' check is a seperate function (and before the validity test),
|
# EXTRACT & ACT
|
||||||
is because even if it's not valid to use it as a fresh ticket, it may already be used with a billing code to update.
|
###################################
|
||||||
"""
|
valid = raw_data_dict.get("valid", None)
|
||||||
|
billing_code = raw_data_dict.get("billing_code", None)
|
||||||
|
message = raw_data_dict.get("message", None)
|
||||||
|
|
||||||
if is_it_valid:
|
logger.info(f"Was the ticket use valid? {valid}")
|
||||||
logger.debug(f"[USE TICKET ORCHESTRATOR] The API server returned a valid billing code.")
|
|
||||||
|
# GOOD OR ALREADY USED
|
||||||
|
if valid or message == "already_used":
|
||||||
|
logger.info("This is a valid use, or a previously valid use that's still good.")
|
||||||
|
|
||||||
# update the ticket tracker:
|
# update the ticket tracker:
|
||||||
status_update = update_value_in_json_with_two_values(
|
saved = update_ticket_tracker(
|
||||||
ticket_tracker_path, which_ticket, "status", "used"
|
which_ticket=which_ticket,
|
||||||
|
status="used",
|
||||||
|
location=which_location,
|
||||||
|
subscription=billing_code
|
||||||
)
|
)
|
||||||
location_update = update_value_in_json_with_two_values(
|
|
||||||
ticket_tracker_path, which_ticket, "location", which_location
|
|
||||||
)
|
|
||||||
subscription_update = update_value_in_json_with_two_values(
|
|
||||||
ticket_tracker_path, which_ticket, "subscription", billing_code
|
|
||||||
)
|
|
||||||
if billing_code is not None:
|
|
||||||
make_sure_sub_saved(
|
|
||||||
subscription_update, which_ticket, billing_code, billing_folder
|
|
||||||
)
|
|
||||||
return {"valid": True, "billing_code": billing_code}
|
|
||||||
|
|
||||||
if "message" not in raw_data_dict:
|
# Emergency:
|
||||||
return {"valid": False, "message": "invalid_format"}
|
if not saved:
|
||||||
|
emergency_save_billing(
|
||||||
message = raw_data_dict.get("message")
|
which_ticket=which_ticket,
|
||||||
|
billing_code=billing_code
|
||||||
if message == "already_used":
|
|
||||||
logger.debug(f"[USE TICKET ORCHESTRATOR] This billing code has already been used, but it is still potentially still valid.")
|
|
||||||
|
|
||||||
# update the ticket tracker to reflect that it's already used with that billing code:
|
|
||||||
status_update = update_value_in_json_with_two_values(
|
|
||||||
ticket_tracker_path, which_ticket, "status", "used"
|
|
||||||
)
|
|
||||||
location_update = update_value_in_json_with_two_values(
|
|
||||||
ticket_tracker_path, which_ticket, "location", which_location
|
|
||||||
)
|
|
||||||
subscription_update = update_value_in_json_with_two_values(
|
|
||||||
ticket_tracker_path, which_ticket, "subscription", billing_code
|
|
||||||
)
|
|
||||||
if billing_code is not None:
|
|
||||||
make_sure_sub_saved(
|
|
||||||
subscription_update, which_ticket, billing_code, billing_folder
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return raw_data_dict
|
# even if not saved return it back, so it can be saved with the profile by other functions,
|
||||||
|
return Result(valid=True, data=billing_code)
|
||||||
|
|
||||||
|
# EXPIRED
|
||||||
|
if not valid and message == "expired":
|
||||||
|
logger.error(f"Expired Ticket! Wiping slot {which_ticket}")
|
||||||
|
saved = update_ticket_tracker(
|
||||||
|
which_ticket=which_ticket,
|
||||||
|
status="unused",
|
||||||
|
location=None,
|
||||||
|
subscription=None
|
||||||
|
)
|
||||||
|
if not saved:
|
||||||
|
logger.error(f"Failed to save {which_ticket} is expired! That's okay the data is worthless to save.")
|
||||||
|
return Result(valid=False, error_type=ResultError.SUBSCRIPTION, message=f"This ticket {which_ticket}'s subscription is expired.")
|
||||||
|
|
||||||
|
# UNKNOWN
|
||||||
|
return Result(valid=False, error_type=ResultError.UNKNOWN, message=f"Unknown error with this data: {raw_data_dict}. {CUSTOMER_SERVICE_MSG}")
|
||||||
|
|
||||||
|
|
||||||
def make_sure_sub_saved(
|
def emergency_save_billing(
|
||||||
subscription_update: bool,
|
|
||||||
which_ticket: int,
|
which_ticket: int,
|
||||||
billing_code: str,
|
billing_code: str
|
||||||
billing_folder: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
docstring = """
|
||||||
# if it failed to save the subscription to ticket tracker
|
If ticket tracker fails to save it to the JSON. Emergency save to a random text file.
|
||||||
if subscription_update == False:
|
"""
|
||||||
logger.debug(
|
logger.error(docstring)
|
||||||
"We failed to save the code the subscription inside the use ticket orchestrator's make_sure_sub_saved"
|
which_ticket_as_str = str(which_ticket)
|
||||||
)
|
write_string_to_text_file(
|
||||||
error_msg = f"Save the code for {which_ticket} of {billing_code}"
|
billing_code,
|
||||||
logger.error(error_msg, exc_info=True)
|
f"{Constants.HV_TICKETING_CONFIG_HOME}/emergency_code_for_profile_{which_ticket_as_str}.txt",
|
||||||
which_ticket_as_str = str(which_ticket)
|
)
|
||||||
write_string_to_text_file(
|
|
||||||
billing_code,
|
|
||||||
f"{billing_folder}/emergency_code_for_profile_{which_ticket_as_str}.txt",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue