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.models.session.SessionState import SessionState
|
||||||
|
from core.controllers.ProfileController import ProfileController
|
||||||
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
|
|
||||||
|
|
||||||
class SessionStateController:
|
class SessionStateController:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(id: int):
|
def get(id: int):
|
||||||
return SessionState.find_by_id(id)
|
return SessionState.find_by_id(id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_or_new(id: int):
|
def get_or_new(id: int):
|
||||||
|
|
||||||
session_state = SessionStateController.get(id)
|
session_state = SessionStateController.get(id)
|
||||||
|
|
||||||
if session_state is None:
|
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
|
return session_state
|
||||||
|
|
||||||
|
|
@ -28,3 +35,46 @@ class SessionStateController:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_or_create(session_state):
|
def update_or_create(session_state):
|
||||||
session_state.save()
|
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.system.SystemState import SystemState
|
||||||
from core.models.enum_models import ProtocolChoice
|
from core.models.enum_models import ProtocolChoice
|
||||||
|
# from core.controllers.ProfileController import ProfileController
|
||||||
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
|
|
||||||
|
|
||||||
class SystemStateController:
|
class SystemStateController:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get():
|
def get():
|
||||||
return SystemState.get()
|
return SystemState.get()
|
||||||
|
|
@ -13,17 +15,49 @@ class SystemStateController:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(profile_id: int, firewalled: bool, dns_set: bool, protocol: ProtocolChoice, process_id: int = None) -> SystemState:
|
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)
|
||||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
|
||||||
else:
|
|
||||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol)
|
|
||||||
current_state.save()
|
current_state.save()
|
||||||
|
|
||||||
|
# Sync to Repo
|
||||||
|
ProfileStateRepository().sync_system_state(current_state)
|
||||||
return current_state
|
return current_state
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_or_create(system_state):
|
def update_or_create(system_state):
|
||||||
system_state.save()
|
system_state.save()
|
||||||
|
# Sync to Repo
|
||||||
|
ProfileStateRepository().sync_system_state(system_state)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dissolve():
|
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.ConnectionController import ConnectionController
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
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:
|
for port_number in session_state.network_port_numbers.tor:
|
||||||
ConnectionController.terminate_tor_session_connection(port_number)
|
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:
|
if profile_observer is not None:
|
||||||
|
|
||||||
|
|
@ -250,11 +250,20 @@ def disable_profile(
|
||||||
else:
|
else:
|
||||||
raise ProfileDeactivationError('Unsupported protocol.')
|
raise ProfileDeactivationError('Unsupported protocol.')
|
||||||
# ================= UPDATE UI ================
|
# ================= 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:
|
if profile_observer is not None:
|
||||||
profile_observer.notify('disabled', profile, dict(
|
profile_observer.notify('disabled', profile, dict(
|
||||||
explicitly=explicitly,
|
explicitly=explicitly,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# return profile type for UI:
|
||||||
|
return Result(valid=True, data=profile.type)
|
||||||
|
|
||||||
except ConnectionTerminationError:
|
except ConnectionTerminationError:
|
||||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -313,16 +322,3 @@ def destroy_profile(
|
||||||
profile_observer.notify('destroyed', profile)
|
profile_observer.notify('destroyed', profile)
|
||||||
|
|
||||||
return Result(valid=True, message="Deleted 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