State is cached via a repo
This commit is contained in:
parent
469268651b
commit
e8373cf0ab
6 changed files with 216 additions and 25 deletions
|
|
@ -1,19 +1,26 @@
|
|||
from core.models.session.SessionState import SessionState
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.infrastructure.global_state import ProfileStateRepository
|
||||
|
||||
|
||||
class SessionStateController:
|
||||
|
||||
@staticmethod
|
||||
def get(id: int):
|
||||
return SessionState.find_by_id(id)
|
||||
|
||||
@staticmethod
|
||||
def get_or_new(id: int):
|
||||
|
||||
session_state = SessionStateController.get(id)
|
||||
|
||||
if session_state is None:
|
||||
return SessionState(id)
|
||||
# Update the Singleton Repo
|
||||
repo = ProfileStateRepository()
|
||||
repo.sync_session_added(id)
|
||||
|
||||
# Create and save the session
|
||||
session_state = SessionState(id)
|
||||
session_state.save()
|
||||
return session_state
|
||||
|
||||
return session_state
|
||||
|
||||
|
|
@ -28,3 +35,46 @@ class SessionStateController:
|
|||
@staticmethod
|
||||
def update_or_create(session_state):
|
||||
session_state.save()
|
||||
# Ensure repo knows this session exists
|
||||
ProfileStateRepository().sync_session_added(session_state.id)
|
||||
|
||||
@staticmethod
|
||||
def remove(profile_id: int):
|
||||
SessionState.dissolve(profile_id)
|
||||
ProfileStateRepository().sync_session_removed(profile_id)
|
||||
|
||||
|
||||
# class SessionStateController:
|
||||
|
||||
# @staticmethod
|
||||
# def get(id: int):
|
||||
# return SessionState.find_by_id(id)
|
||||
|
||||
# @staticmethod
|
||||
# def get_or_new(id: int):
|
||||
|
||||
# session_state = SessionStateController.get(id)
|
||||
|
||||
# if session_state is None:
|
||||
|
||||
# # update global state:
|
||||
# global_state = GlobalState()
|
||||
# if id not in global_state.session_profile_ids: # sanity check.
|
||||
# global_state.session_profile_ids.append(id)
|
||||
|
||||
# # create new:
|
||||
# return SessionState(id)
|
||||
|
||||
# return session_state
|
||||
|
||||
# @staticmethod
|
||||
# def exists(id: int):
|
||||
# return SessionState.exists(id)
|
||||
|
||||
# @staticmethod
|
||||
# def all():
|
||||
# return SessionState.all()
|
||||
|
||||
# @staticmethod
|
||||
# def update_or_create(session_state):
|
||||
# session_state.save()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
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
|
||||
|
||||
|
||||
class SystemStateController:
|
||||
|
||||
@staticmethod
|
||||
def get():
|
||||
return SystemState.get()
|
||||
|
|
@ -13,17 +15,49 @@ class SystemStateController:
|
|||
|
||||
@staticmethod
|
||||
def create(profile_id: int, firewalled: bool, dns_set: bool, protocol: ProtocolChoice, process_id: int = None) -> SystemState:
|
||||
if process_id:
|
||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
||||
else:
|
||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol)
|
||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
||||
current_state.save()
|
||||
|
||||
# Sync to Repo
|
||||
ProfileStateRepository().sync_system_state(current_state)
|
||||
return current_state
|
||||
|
||||
@staticmethod
|
||||
def update_or_create(system_state):
|
||||
system_state.save()
|
||||
# Sync to Repo
|
||||
ProfileStateRepository().sync_system_state(system_state)
|
||||
|
||||
@staticmethod
|
||||
def dissolve():
|
||||
return SystemState.dissolve()
|
||||
res = SystemState.dissolve()
|
||||
# Sync to Repo: clear system ID
|
||||
ProfileStateRepository().global_state.systemwide_profile_id = None
|
||||
return res
|
||||
|
||||
# class SystemStateController:
|
||||
|
||||
# @staticmethod
|
||||
# def get():
|
||||
# return SystemState.get()
|
||||
|
||||
# @staticmethod
|
||||
# def exists():
|
||||
# return SystemState.exists()
|
||||
|
||||
# @staticmethod
|
||||
# def create(profile_id: int, firewalled: bool, dns_set: bool, protocol: ProtocolChoice, process_id: int = None) -> SystemState:
|
||||
# if process_id:
|
||||
# current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
||||
# else:
|
||||
# current_state = SystemState(profile_id, firewalled, dns_set, protocol)
|
||||
# current_state.save()
|
||||
# return current_state
|
||||
|
||||
# @staticmethod
|
||||
# def update_or_create(system_state):
|
||||
# system_state.save()
|
||||
|
||||
# @staticmethod
|
||||
# def dissolve():
|
||||
# return SystemState.dissolve()
|
||||
|
|
|
|||
59
core/infrastructure/global_state.py
Normal file
59
core/infrastructure/global_state.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
from core.models.system.SystemState import SystemState
|
||||
from core.models.session.SessionState import SessionState
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class GlobalState:
|
||||
systemwide_active: bool = False
|
||||
firewalled: bool = False
|
||||
dns_set: bool = False
|
||||
systemwide_profile_id: Optional[int] = None
|
||||
session_profile_ids: list[int] = field(default_factory=list)
|
||||
verified: bool = False
|
||||
|
||||
class ProfileStateRepository:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(ProfileStateRepository, cls).__new__(cls)
|
||||
cls._instance._initialize()
|
||||
return cls._instance
|
||||
|
||||
def _initialize(self):
|
||||
self.global_state = GlobalState()
|
||||
# Initial load: Sync memory with whatever is currently on disk
|
||||
sys = SystemState.get()
|
||||
if sys:
|
||||
self.global_state.systemwide_profile_id = sys.profile_id
|
||||
self.global_state.firewalled = sys.firewalled
|
||||
self.global_state.dns_set = sys.dns_set
|
||||
|
||||
# Load existing session IDs from disk into the list
|
||||
self.global_state.session_profile_ids = [s.id for s in SessionState.all()]
|
||||
|
||||
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
|
||||
|
||||
def sync_session_added(self, profile_id: int):
|
||||
"""Called by SessionStateController to update memory cache."""
|
||||
if profile_id not in self.global_state.session_profile_ids:
|
||||
self.global_state.session_profile_ids.append(profile_id)
|
||||
|
||||
def sync_session_removed(self, profile_id: int):
|
||||
"""Called by SessionStateController to update memory cache."""
|
||||
if profile_id in self.global_state.session_profile_ids:
|
||||
self.global_state.session_profile_ids.remove(profile_id)
|
||||
|
||||
def is_profile_active(self, profile_id: int) -> bool:
|
||||
# Check system profile
|
||||
if self.global_state.systemwide_active and self.global_state.systemwide_profile_id == profile_id:
|
||||
return True
|
||||
# Check session profiles
|
||||
return profile_id in self.global_state.session_profile_ids
|
||||
|
||||
27
core/infrastructure/global_state_coordinator.py
Normal file
27
core/infrastructure/global_state_coordinator.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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 evaluate_state_on_startup() -> Result:
|
||||
"""When the app first starts, evaluate pre-existing state"""
|
||||
|
||||
#-----------------------
|
||||
# SYSTEM-WIDE
|
||||
#-----------------------
|
||||
systemwide_active = SystemStateController.get()
|
||||
if systemwide_active:
|
||||
error_msg = "A pre-existing systemwide state is active."
|
||||
logger.error(error_msg)
|
||||
return Result(valid=False, error_type=ResultError.STATE_CONFLICT, message=error_msg)
|
||||
|
||||
#-----------------------
|
||||
# SESSION
|
||||
#-----------------------
|
||||
active_session_profiles = SessionStateController.all()
|
||||
if active_session_profiles:
|
||||
for each_profile in active_session_profiles:
|
||||
logger.info(f"Removing this profile {active_session_profiles} from session since it's a fresh restart.")
|
||||
SessionStateController.remove(each_profile.id)
|
||||
|
||||
return Result(valid=True)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
from core.models.enum_models import ProtocolChoice, valid_enum
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
||||
|
|
@ -206,7 +205,8 @@ def disable_profile(
|
|||
for port_number in session_state.network_port_numbers.tor:
|
||||
ConnectionController.terminate_tor_session_connection(port_number)
|
||||
|
||||
session_state.dissolve(session_state.id)
|
||||
# session_state.dissolve(session_state.id)
|
||||
SessionStateController.remove(profile.id)
|
||||
|
||||
if profile_observer is not None:
|
||||
|
||||
|
|
@ -250,11 +250,20 @@ def disable_profile(
|
|||
else:
|
||||
raise ProfileDeactivationError('Unsupported protocol.')
|
||||
# ================= UPDATE UI ================
|
||||
# if it made it this far, it worked in theory.
|
||||
# if it made it this far, it worked (in theory).
|
||||
|
||||
# wipe state:
|
||||
SystemStateController.remove(profile.id)
|
||||
|
||||
# update ui:
|
||||
if profile_observer is not None:
|
||||
profile_observer.notify('disabled', profile, dict(
|
||||
explicitly=explicitly,
|
||||
))
|
||||
|
||||
# return profile type for UI:
|
||||
return Result(valid=True, data=profile.type)
|
||||
|
||||
except ConnectionTerminationError:
|
||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||
except ValueError:
|
||||
|
|
@ -313,16 +322,3 @@ def destroy_profile(
|
|||
profile_observer.notify('destroyed', profile)
|
||||
|
||||
return Result(valid=True, message="Deleted Profile")
|
||||
|
||||
|
||||
def get_protocol_enum(profile_id: int) -> ProtocolChoice|bool:
|
||||
"""Return the Enum for a given Profile's Protocol Choice"""
|
||||
|
||||
profile = ProfileController.get(profile_id)
|
||||
|
||||
protocol_string = profile.connection.code
|
||||
|
||||
if not valid_enum(protocol_string, ProtocolChoice):
|
||||
return False
|
||||
else:
|
||||
return ProtocolChoice(protocol_string)
|
||||
25
core/utils/singleton.py
Normal file
25
core/utils/singleton.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
from functools import wraps
|
||||
|
||||
def singleton(cls):
|
||||
instances = {}
|
||||
|
||||
@wraps(cls)
|
||||
def get_instance(*args, **kwargs):
|
||||
instance_id = kwargs.get('instance_id')
|
||||
|
||||
# If instance_id provided, use localized mode (one instance per ID)
|
||||
# Otherwise, use global mode (single instance)
|
||||
key = (cls, instance_id) if instance_id else cls
|
||||
|
||||
if key not in instances:
|
||||
print(f"Creating new instance with key: {key}")
|
||||
instances[key] = cls(*args, **kwargs)
|
||||
else:
|
||||
print(f"Reusing existing instance with key: {key}")
|
||||
|
||||
# either way:
|
||||
return instances[key]
|
||||
|
||||
return get_instance
|
||||
|
||||
Loading…
Reference in a new issue