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.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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
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):
|
||||
|
||||
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 = []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue