Introduced a Global State Management system, with a repo cache and notifications to the UI.

This commit is contained in:
SimplifiedPrivacy 2026-10-18 21:16:52 -04:00
parent 06e5470c4d
commit fcace28890
12 changed files with 190 additions and 23 deletions

View file

@ -5,6 +5,7 @@ from core.errors.logger import logger
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError
from core.models.BaseProfile import BaseProfile as Profile
from core.models.BaseProfile import ProfileType
from core.models.Subscription import Subscription
from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemProfile import SystemProfile
@ -69,6 +70,17 @@ class ProfileController:
ProfileController._repo.invalidate(profile_id=profile_id)
@staticmethod
def what_type_is_profile(profile_id: int) -> ProfileType:
all_data = ProfileController._repo.get_all()
given_profile = all_data[profile_id]
return given_profile.type
# print(f"all_data[{profile_id}] is {all_data[profile_id]}")
# @staticmethod
# def attach_subscription(profile: Union[SessionProfile, SystemProfile], subscription: Subscription):

View file

@ -1,6 +1,7 @@
from core.models.session.SessionState import SessionState
from core.controllers.ProfileController import ProfileController
from core.infrastructure.global_state import ProfileStateRepository
from core.observers.Notifications import Notifications, EventType
class SessionStateController:
@ -34,11 +35,25 @@ class SessionStateController:
@staticmethod
def update_or_create(session_state):
# save to model:
session_state.save()
# get profile id:
profile_id = session_state.id
# Ensure repo knows this session exists
ProfileStateRepository().sync_session_added(session_state.id)
ProfileStateRepository().sync_session_added(profile_id)
# notify UI:
Notifications.emit(EventType.SESSION_PROFILE_ENABLE, profile_id)
@staticmethod
def remove(profile_id: int):
# save to model:
SessionState.dissolve(profile_id)
# cache:
ProfileStateRepository().sync_session_removed(profile_id)
# notify UI:
Notifications.emit(EventType.SESSION_PROFILE_DISABLE, profile_id)

View file

@ -2,7 +2,8 @@ from core.models.system.SystemState import SystemState
from core.models.enum_models import ProtocolChoice
# from core.controllers.ProfileController import ProfileController
from core.infrastructure.global_state import ProfileStateRepository
from core.observers.Notifications import Notifications, EventType
from core.errors.logger import logger
class SystemStateController:
@staticmethod
@ -15,23 +16,64 @@ class SystemStateController:
@staticmethod
def create(profile_id: int, firewalled: bool, dns_set: bool, protocol: ProtocolChoice, process_id: int = None) -> SystemState:
# model on disk
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
current_state.save()
# Sync to Repo
# cache (Sync to Repo)
ProfileStateRepository().sync_system_state(current_state)
return current_state
@staticmethod
def update_or_create(system_state):
"""Update the model & cache for a systemwide connection"""
# model
system_state.save()
# Sync to Repo
# cache (Sync to Repo)
ProfileStateRepository().sync_system_state(system_state)
# notify is done separately because this function can be used to modify state in the event of firewall failure.
@staticmethod
def dissolve():
res = SystemState.dissolve()
# Sync to Repo: clear system ID
ProfileStateRepository().global_state.systemwide_profile_id = None
return res
# get singleton:
state_repo = ProfileStateRepository()
# extract current active id:
profile_id = state_repo.global_state.systemwide_profile_id
# model wipes data on disk:
SystemState.dissolve()
# Wipe the Repo's Cache:
state_repo.turn_off_system_connection()
# notify UI with that past id:
Notifications.emit(EventType.SYSTEM_DISABLE, profile_id)
# old version:
# ProfileStateRepository().global_state.systemwide_profile_id = None
# @staticmethod
# def notify_on():
# """Notify the notifications that systemwide state shows on"""
# # get singleton:
# state_repo = ProfileStateRepository()
# # extract current data:
# profile_id = state_repo.global_state.systemwide_profile_id
# systemwide_active = state_repo.global_state.systemwide_active
# # double check, confirm:
# if systemwide_active and systemwide_active:
# # notify UI:
# Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
# else:
# logger.error("Critical Error, notify_on was called out of place.")
# Notifications.emit(EventType.SYSTEM_DISABLE, profile_id)

View file

@ -6,6 +6,7 @@ from core.Errors import (
InvalidSubscriptionError, MissingSubscriptionError, ProfileActivationError,
ProfileModificationError, ProfileStateConflictError, UnsupportedApplicationVersionError,
)
from core.utils.type_checker import type_checker
from typing import Type, Optional
@ -17,7 +18,7 @@ ERROR_TO_UI_MESSAGE = {
ProfileModificationError: "WireGuard configuration could not be attached.",
}
@type_checker
def translate_error(error: Type[Exception], profile_id: int) -> Result:
"""Translate legacy core errors into actions and/or messages via Result Objects."""
logger.error(str(error))
@ -66,7 +67,7 @@ def translate_error(error: Type[Exception], profile_id: int) -> Result:
return Result(valid=False, error_type=ResultError.UNKNOWN, message="An unknown error occured.")
@type_checker
def get_ignored_exceptions(ignore_state_conflict: Optional[bool] = False):
ignored_as_list = []
endpoint_verification_enabled = ConfigurationController.get_endpoint_verification_enabled()

View file

@ -34,11 +34,22 @@ class ProfileStateRepository:
# Load existing session IDs from disk into the list
self.global_state.session_profile_ids = [s.id for s in SessionState.all()]
def turn_off_system_connection(self):
"""Clear the system-wide memory cache."""
self.global_state.systemwide_profile_id = None
self.global_state.firewalled = False
self.global_state.dns_set = False
self.global_state.systemwide_active = False
def sync_system_state(self, state: SystemState):
"""Called by SystemStateController to update memory cache."""
self.global_state.systemwide_profile_id = state.profile_id
self.global_state.firewalled = state.firewalled
self.global_state.dns_set = state.dns_set
if state.profile_id:
self.global_state.systemwide_active = True
def sync_session_added(self, profile_id: int):
"""Called by SessionStateController to update memory cache."""

View file

@ -1,8 +1,41 @@
from core.infrastructure.global_state import ProfileStateRepository
from core.controllers.SystemStateController import SystemStateController
from core.controllers.SessionStateController import SessionStateController
from core.models.Result import Result, ResultError
from core.errors.logger import logger
def is_systemwide_on() -> bool:
"""Returns a boolean if systemwide is connected. True is Yes, False is No"""
state_repo = ProfileStateRepository()
return state_repo.global_state.systemwide_active
def connected_system_profile_id() -> int|None:
"""Returns a single number of the connected system profile"""
state_repo = ProfileStateRepository()
print(f"state_repo is {state_repo.global_state}")
return state_repo.global_state.systemwide_profile_id
def connected_session_profile_ids() -> list|None:
"""Returns a list of the currently connected session profile IDs"""
state_repo = ProfileStateRepository()
return state_repo.global_state.session_profile_ids
def is_connected(profile_id: int) -> bool:
"""Returns a boolean if a given profile is connected"""
# system-wide:
systemwide_profile_id = connected_system_profile_id()
if profile_id == systemwide_profile_id:
return True
# session:
state_repo = ProfileStateRepository()
return state_repo.is_profile_active(profile_id)
def evaluate_state_on_startup() -> Result:
"""When the app first starts, evaluate pre-existing state"""
@ -25,3 +58,5 @@ def evaluate_state_on_startup() -> Result:
SessionStateController.remove(each_profile.id)
return Result(valid=True)

View file

@ -4,9 +4,9 @@ from essentials.observers.BaseObserver import BaseObserver
class ClientObserver(BaseObserver):
def __init__(self):
self.on_synchronizing = []
self.on_synchronized = []
self.on_updating = []
self.on_update_progressing = []
self.on_updated = []
pass
# self.on_synchronizing = []
# self.on_synchronized = []
# self.on_updating = []
# self.on_update_progressing = []
# self.on_updated = []

View file

@ -0,0 +1,19 @@
class EventBus:
_subscribers = {}
@classmethod
def subscribe(cls, event_type, callback):
if event_type not in cls._subscribers:
cls._subscribers[event_type] = []
cls._subscribers[event_type].append(callback)
@classmethod
def emit(cls, event_type, data=None):
for callback in cls._subscribers.get(event_type, []):
callback(data)
@classmethod
def unsubscribe(cls, event_type, callback):
if event_type in cls._subscribers:
cls._subscribers[event_type].remove(callback)

View file

@ -0,0 +1,28 @@
from enum import Enum, auto
class EventType(Enum):
SESSION_PROFILE_ENABLE = auto()
SESSION_PROFILE_DISABLE = auto()
SYSTEM_ENABLE = auto()
SYSTEM_DISABLE = auto()
PROFILE_CREATED = auto()
PROFILE_DELETED = auto()
class Notifications:
_subscribers = {}
@classmethod
def subscribe(cls, event_type: EventType, callback):
if event_type not in cls._subscribers:
cls._subscribers[event_type] = []
cls._subscribers[event_type].append(callback)
@classmethod
def emit(cls, event_type: EventType, data=None):
for callback in cls._subscribers.get(event_type, []):
callback(data)

View file

@ -4,9 +4,9 @@ from essentials.observers.BaseObserver import BaseObserver
class ProfileObserver(BaseObserver):
def __init__(self):
self.on_created = []
self.on_updated = []
self.on_destroyed = []
self.on_disabled = []
self.on_enabled = []
pass
# self.on_created = []
# self.on_updated = []
# self.on_destroyed = []
# self.on_disabled = []
# self.on_enabled = []

View file

@ -11,6 +11,7 @@ from core.services.networking.general_connection_tools import ip
from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file
from core.utils.search_tools import search_for_phrase
from core.utils.basic_operations.does_file_exist import does_file_exist
from core.observers.Notifications import Notifications, EventType
from core.models.Result import Result, ResultError
from core.errors.logger import logger
@ -348,6 +349,7 @@ def start_singbox(
connection_observer.notify("connecting", "DNS Changed.")
# raises error if not okay.
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
# ============= CONCLUSION =============
return Result(valid=True, data=process_id)

View file

@ -4,6 +4,7 @@ from core.services.networking.general_connection_tools.testing_evaluating import
from core.services.keys_and_verifications.endpoint_verification import verify_wireguard_endpoint
from core.models.Result import Result, ResultError
from core.services.networking.systemwide.systemwide_utils import extract_wg_interface_name, check_system_prereqs
from core.observers.Notifications import Notifications, EventType
# from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip, extract_dns
from core.services.networking.systemwide import killswitch
@ -211,6 +212,7 @@ def __establish_system_connection(
current_state.dns_set = did_dns_work
SystemStateController.update_or_create(current_state)
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
return Result(valid=True, data=current_state)