Introduced a Global State Management system, with a repo cache and notifications to the UI.
This commit is contained in:
parent
06e5470c4d
commit
fcace28890
12 changed files with 190 additions and 23 deletions
|
|
@ -5,6 +5,7 @@ from core.errors.logger import logger
|
||||||
|
|
||||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError
|
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError
|
||||||
from core.models.BaseProfile import BaseProfile as Profile
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
|
from core.models.BaseProfile import ProfileType
|
||||||
from core.models.Subscription import Subscription
|
from core.models.Subscription import Subscription
|
||||||
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
|
||||||
|
|
@ -69,6 +70,17 @@ class ProfileController:
|
||||||
ProfileController._repo.invalidate(profile_id=profile_id)
|
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
|
# @staticmethod
|
||||||
# def attach_subscription(profile: Union[SessionProfile, SystemProfile], subscription: Subscription):
|
# def attach_subscription(profile: Union[SessionProfile, SystemProfile], subscription: Subscription):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from core.models.session.SessionState import SessionState
|
from core.models.session.SessionState import SessionState
|
||||||
from core.controllers.ProfileController import ProfileController
|
from core.controllers.ProfileController import ProfileController
|
||||||
from core.infrastructure.global_state import ProfileStateRepository
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
|
from core.observers.Notifications import Notifications, EventType
|
||||||
|
|
||||||
|
|
||||||
class SessionStateController:
|
class SessionStateController:
|
||||||
|
|
@ -34,11 +35,25 @@ class SessionStateController:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_or_create(session_state):
|
def update_or_create(session_state):
|
||||||
|
# save to model:
|
||||||
session_state.save()
|
session_state.save()
|
||||||
|
|
||||||
|
# get profile id:
|
||||||
|
profile_id = session_state.id
|
||||||
|
|
||||||
# Ensure repo knows this session exists
|
# 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
|
@staticmethod
|
||||||
def remove(profile_id: int):
|
def remove(profile_id: int):
|
||||||
|
# save to model:
|
||||||
SessionState.dissolve(profile_id)
|
SessionState.dissolve(profile_id)
|
||||||
|
|
||||||
|
# cache:
|
||||||
ProfileStateRepository().sync_session_removed(profile_id)
|
ProfileStateRepository().sync_session_removed(profile_id)
|
||||||
|
|
||||||
|
# notify UI:
|
||||||
|
Notifications.emit(EventType.SESSION_PROFILE_DISABLE, profile_id)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ 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.controllers.ProfileController import ProfileController
|
||||||
from core.infrastructure.global_state import ProfileStateRepository
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
|
from core.observers.Notifications import Notifications, EventType
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
class SystemStateController:
|
class SystemStateController:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -15,23 +16,64 @@ 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:
|
||||||
|
|
||||||
|
# model on disk
|
||||||
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
current_state = SystemState(profile_id, firewalled, dns_set, protocol, process_id)
|
||||||
current_state.save()
|
current_state.save()
|
||||||
|
|
||||||
# Sync to Repo
|
# cache (Sync to Repo)
|
||||||
ProfileStateRepository().sync_system_state(current_state)
|
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):
|
||||||
|
"""Update the model & cache for a systemwide connection"""
|
||||||
|
|
||||||
|
# model
|
||||||
system_state.save()
|
system_state.save()
|
||||||
# Sync to Repo
|
|
||||||
|
# cache (Sync to Repo)
|
||||||
ProfileStateRepository().sync_system_state(system_state)
|
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
|
@staticmethod
|
||||||
def dissolve():
|
def dissolve():
|
||||||
res = SystemState.dissolve()
|
# get singleton:
|
||||||
# Sync to Repo: clear system ID
|
state_repo = ProfileStateRepository()
|
||||||
ProfileStateRepository().global_state.systemwide_profile_id = None
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from core.Errors import (
|
||||||
InvalidSubscriptionError, MissingSubscriptionError, ProfileActivationError,
|
InvalidSubscriptionError, MissingSubscriptionError, ProfileActivationError,
|
||||||
ProfileModificationError, ProfileStateConflictError, UnsupportedApplicationVersionError,
|
ProfileModificationError, ProfileStateConflictError, UnsupportedApplicationVersionError,
|
||||||
)
|
)
|
||||||
|
from core.utils.type_checker import type_checker
|
||||||
|
|
||||||
|
|
||||||
from typing import Type, Optional
|
from typing import Type, Optional
|
||||||
|
|
@ -17,7 +18,7 @@ ERROR_TO_UI_MESSAGE = {
|
||||||
ProfileModificationError: "WireGuard configuration could not be attached.",
|
ProfileModificationError: "WireGuard configuration could not be attached.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type_checker
|
||||||
def translate_error(error: Type[Exception], profile_id: int) -> Result:
|
def translate_error(error: Type[Exception], profile_id: int) -> Result:
|
||||||
"""Translate legacy core errors into actions and/or messages via Result Objects."""
|
"""Translate legacy core errors into actions and/or messages via Result Objects."""
|
||||||
logger.error(str(error))
|
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.")
|
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):
|
def get_ignored_exceptions(ignore_state_conflict: Optional[bool] = False):
|
||||||
ignored_as_list = []
|
ignored_as_list = []
|
||||||
endpoint_verification_enabled = ConfigurationController.get_endpoint_verification_enabled()
|
endpoint_verification_enabled = ConfigurationController.get_endpoint_verification_enabled()
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,22 @@ class ProfileStateRepository:
|
||||||
# Load existing session IDs from disk into the list
|
# Load existing session IDs from disk into the list
|
||||||
self.global_state.session_profile_ids = [s.id for s in SessionState.all()]
|
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):
|
def sync_system_state(self, state: SystemState):
|
||||||
"""Called by SystemStateController to update memory cache."""
|
"""Called by SystemStateController to update memory cache."""
|
||||||
self.global_state.systemwide_profile_id = state.profile_id
|
self.global_state.systemwide_profile_id = state.profile_id
|
||||||
self.global_state.firewalled = state.firewalled
|
self.global_state.firewalled = state.firewalled
|
||||||
self.global_state.dns_set = state.dns_set
|
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):
|
def sync_session_added(self, profile_id: int):
|
||||||
"""Called by SessionStateController to update memory cache."""
|
"""Called by SessionStateController to update memory cache."""
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,41 @@
|
||||||
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
from core.controllers.SystemStateController import SystemStateController
|
from core.controllers.SystemStateController import SystemStateController
|
||||||
from core.controllers.SessionStateController import SessionStateController
|
from core.controllers.SessionStateController import SessionStateController
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
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:
|
def evaluate_state_on_startup() -> Result:
|
||||||
"""When the app first starts, evaluate pre-existing state"""
|
"""When the app first starts, evaluate pre-existing state"""
|
||||||
|
|
||||||
|
|
@ -24,4 +57,6 @@ def evaluate_state_on_startup() -> Result:
|
||||||
logger.info(f"Removing this profile {active_session_profiles} from session since it's a fresh restart.")
|
logger.info(f"Removing this profile {active_session_profiles} from session since it's a fresh restart.")
|
||||||
SessionStateController.remove(each_profile.id)
|
SessionStateController.remove(each_profile.id)
|
||||||
|
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ from essentials.observers.BaseObserver import BaseObserver
|
||||||
class ClientObserver(BaseObserver):
|
class ClientObserver(BaseObserver):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
pass
|
||||||
self.on_synchronizing = []
|
# self.on_synchronizing = []
|
||||||
self.on_synchronized = []
|
# self.on_synchronized = []
|
||||||
self.on_updating = []
|
# self.on_updating = []
|
||||||
self.on_update_progressing = []
|
# self.on_update_progressing = []
|
||||||
self.on_updated = []
|
# self.on_updated = []
|
||||||
|
|
|
||||||
19
core/observers/EventBus.py
Normal file
19
core/observers/EventBus.py
Normal 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)
|
||||||
28
core/observers/Notifications.py
Normal file
28
core/observers/Notifications.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,9 +4,9 @@ from essentials.observers.BaseObserver import BaseObserver
|
||||||
class ProfileObserver(BaseObserver):
|
class ProfileObserver(BaseObserver):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
pass
|
||||||
self.on_created = []
|
# self.on_created = []
|
||||||
self.on_updated = []
|
# self.on_updated = []
|
||||||
self.on_destroyed = []
|
# self.on_destroyed = []
|
||||||
self.on_disabled = []
|
# self.on_disabled = []
|
||||||
self.on_enabled = []
|
# self.on_enabled = []
|
||||||
|
|
|
||||||
|
|
@ -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.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.search_tools import search_for_phrase
|
||||||
from core.utils.basic_operations.does_file_exist import does_file_exist
|
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.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
@ -348,6 +349,7 @@ def start_singbox(
|
||||||
connection_observer.notify("connecting", "DNS Changed.")
|
connection_observer.notify("connecting", "DNS Changed.")
|
||||||
# raises error if not okay.
|
# raises error if not okay.
|
||||||
|
|
||||||
|
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
|
||||||
# ============= CONCLUSION =============
|
# ============= CONCLUSION =============
|
||||||
return Result(valid=True, data=process_id)
|
return Result(valid=True, data=process_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.services.keys_and_verifications.endpoint_verification import verify_wireguard_endpoint
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.services.networking.systemwide.systemwide_utils import extract_wg_interface_name, check_system_prereqs
|
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.general_connection_tools.config_tools import extract_endpoint_ip, extract_dns
|
||||||
from core.services.networking.systemwide import killswitch
|
from core.services.networking.systemwide import killswitch
|
||||||
|
|
@ -211,6 +212,7 @@ def __establish_system_connection(
|
||||||
current_state.dns_set = did_dns_work
|
current_state.dns_set = did_dns_work
|
||||||
SystemStateController.update_or_create(current_state)
|
SystemStateController.update_or_create(current_state)
|
||||||
|
|
||||||
|
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
|
||||||
return Result(valid=True, data=current_state)
|
return Result(valid=True, data=current_state)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue