When a profile is deleted, it now automatically respawns the ticket if it's a ticket profile. Also fixed some bugs with fetching the server's public key with the APIResponse object being used, when it expected a regular dictionary. This is a left-over from the prior transition to API objects.

This commit is contained in:
SimplifiedPrivacy 2026-08-15 17:34:08 -04:00
parent 829feb5939
commit 5ab57ab287
7 changed files with 96 additions and 13 deletions

View file

@ -1,5 +1,10 @@
# Major Change Log: # Major Change Log:
# Respawn on Delete
### Aug 15, 2026
When a profile is deleted, it now automatically respawns the ticket if it's a ticket profile. Also fixed some bugs with fetching the server's public key with the APIResponse object being used, when it expected a regular dictionary. This is a left-over from the prior transition to API objects.
<br/>
# Codes/Tickets # Codes/Tickets
### Aug 15, 2026 ### Aug 15, 2026
The profile object now has a ticket field. It's now none by default. Also the profile object is passed in, for the use of tickets, both for GUI and core. The profile object now has a ticket field. It's now none by default. Also the profile object is passed in, for the use of tickets, both for GUI and core.

View file

@ -7,6 +7,10 @@ from core.services.networking.systemwide import killswitch
from core.services.subscriptions import subscriptions from core.services.subscriptions import subscriptions
from core.errors.exceptions import FirewallError from core.errors.exceptions import FirewallError
from core.models.Result import Result, ResultError from core.models.Result import Result, ResultError
from core.observers.TicketObserver import TicketObserver
from core.controllers.tickets.TicketPrepController import respawn_billing_code_into_ticket
from core.services.prepare_tickets.ticket_tracker import get_tickets_with
from core.errors.logger import logger
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationController import ApplicationController
@ -182,8 +186,47 @@ class ProfileController:
time.sleep(1.0) time.sleep(1.0)
@staticmethod @staticmethod
def destroy(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None): def destroy(profile: Union[SessionProfile, SystemProfile], profile_observer: ProfileObserver = None, ticket_observer: TicketObserver = None, connection_observer: ConnectionObserver = None):
####################################
# DESTROY TICKET
####################################
which_ticket = profile.ticket
# try via lookup:
if not which_ticket:
logger.info(f"Unable to find the ticket # in the profile's native config for {profile.id}. Checking the ticket tracker JSON")
target_subscription = profile.subscription.billing_code
ticket_list = get_tickets_with(target_subscription)
if len(ticket_list) >= 1:
which_ticket = ticket_list[0]
logger.info(f"We got the ticket {which_ticket} for profile {profile.id} from the ticket tracker JSON")
else:
logger.info(f"We were UNABLE to find a ticket with the subscription for profile {profile.id} from the ticket tracker JSON. Proceeding with delete regardless..")
# Regardless of how it was acquired,
if which_ticket:
notification = f"Respawn Started for Ticket {which_ticket}"
logger.info(notification)
ticket_observer.notify("preparing", subject=notification)
respawn_result = respawn_billing_code_into_ticket(
profile=profile,
which_ticket=which_ticket,
ticket_observer=ticket_observer,
connection_observer=connection_observer
)
if respawn_result.valid:
notification = f"Ticket Respawned!"
logger.info(notification)
ticket_observer.notify("preparing", subject=notification)
else:
notification = f"Error with Ticket Respawn!"
logger.error(f"{notification} {respawn_result.error_type} with message: {respawn_result.message}")
ticket_observer.notify("preparing", subject=notification)
####################################
# DESTROY PROFILE
####################################
ProfileController.disable(profile) ProfileController.disable(profile)
profile.delete() profile.delete()

View file

@ -110,7 +110,7 @@ def respawn_billing_code_into_ticket(
which_ticket: int, which_ticket: int,
ticket_observer: TicketObserver, ticket_observer: TicketObserver,
connection_observer: ConnectionObserver connection_observer: ConnectionObserver
) -> bool: ) -> Result:
""" """
Converts a valid VPN billing code, into a valid unused ticket. Converts a valid VPN billing code, into a valid unused ticket.
At the cost of the server expiring the billing code immediately. At the cost of the server expiring the billing code immediately.
@ -128,7 +128,10 @@ def respawn_billing_code_into_ticket(
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing which ticket slot is being respawned") return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing which ticket slot is being respawned")
if not isinstance(which_ticket, int): if not isinstance(which_ticket, int):
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Ticket slot must be a number.") try:
which_ticket = int(which_ticket)
except:
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Ticket slot must be a number.")
ticket_result = prepare_tickets( ticket_result = prepare_tickets(
how_many_profiles=1, how_many_profiles=1,

View file

@ -31,7 +31,6 @@ def get_from_server_and_save(
url = make_url(string_form_of_key_name) url = make_url(string_form_of_key_name)
# public_key_results = get_data_from_server(url, connection_observer)
public_key_results = connect.single_endpoint( public_key_results = connect.single_endpoint(
method="get", method="get",
url=url, url=url,
@ -39,14 +38,22 @@ def get_from_server_and_save(
payload=None payload=None
) )
if public_key_results.valid: if not public_key_results.valid:
# extract: error_msg = f"Error with API getting the Public Key: {public_key_results.error_type} and {public_key_results.message}"
public_key = public_key_results.data logger.error(error_msg)
return {"valid": False, "message": error_msg}
# save it: # extract:
did_it_save = write_string_to_text_file(public_key, file_path) public_key = public_key_results.data
return public_key_results # save it:
did_it_save = write_string_to_text_file(public_key, file_path)
if not did_it_save:
logger.error(f"Error with saving the public key {public_key} with write_string_to_text_file, but still returning data..")
# regardless of saving:
return {"valid": True, "data": public_key}
def get_pub_key( def get_pub_key(

View file

@ -16,7 +16,7 @@ from core.services.helpers.get_which_billing_key import get_which_billing_key
from core.models.session.SessionProfile import SessionProfile from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemProfile import SystemProfile from core.models.system.SystemProfile import SystemProfile
from core.models.Subscription import Subscription # not sure if needed from core.models.Subscription import Subscription # not sure if needed
from core.controllers.ProfileController import ProfileController # from core.controllers.ProfileController import ProfileController
# utils # utils
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file

View file

@ -1,8 +1,10 @@
from core.utils.basic_operations.write_or_read_from_json import read_entire_json, update_json from core.utils.basic_operations.write_or_read_from_json import read_entire_json, update_json
from core.utils.basic_operations.does_file_exist import does_file_exist
from core.errors.exceptions import * from core.errors.exceptions import *
from core.errors.logger import logger from core.errors.logger import logger
from core.Constants import Constants from core.Constants import Constants
# generic
import json import json
import os import os
@ -88,4 +90,23 @@ def wipe_one_ticket_sub(which_ticket: int) -> bool:
filepath=Constants.TICKET_TRACKER_PATH, filepath=Constants.TICKET_TRACKER_PATH,
key_to_add=which_ticket, key_to_add=which_ticket,
value_to_update=wipe_payload value_to_update=wipe_payload
) )
def get_tickets_with(target_subscription: str) -> list:
if not does_file_exist(Constants.TICKET_TRACKER_PATH):
return []
whole_json = read_entire_json(Constants.TICKET_TRACKER_PATH)
if not whole_json:
return []
list_of_matches = []
for each_number in whole_json:
if whole_json[each_number]['subscription'] == target_subscription:
list_of_matches.append(each_number)
return list_of_matches

View file

@ -1,3 +1,5 @@
import os
""" """
Note: this function will accept EITHER raw dicts or string, WITHOUT converting to JSON, Note: this function will accept EITHER raw dicts or string, WITHOUT converting to JSON,
and literally convert to str to save. At a first glance, this seems ridiculous. and literally convert to str to save. At a first glance, this seems ridiculous.
@ -13,16 +15,18 @@ Therefore, we opted for this data storage format for some data types.
""" """
def write_string_to_text_file(content_to_write: str | dict, file_path: str) -> bool: def write_string_to_text_file(content_to_write: str | dict, file_path: str) -> bool:
if content_to_write is None: if content_to_write is None:
return False return False
try: try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, "w") as file: with open(file_path, "w") as file:
file.write(str(content_to_write)) file.write(str(content_to_write))
return True return True
except FileNotFoundError: except FileNotFoundError:
print(f"Error: The file '{file_path}' was not found.") print(f"Error inside of write_string_to_text: The file '{file_path}' was not found.")
return False return False
except IOError: except IOError:
print( print(