diff --git a/assets/yaml_mappings/locations.yaml b/assets/yaml_mappings/locations.yaml new file mode 100644 index 0000000..762af31 --- /dev/null +++ b/assets/yaml_mappings/locations.yaml @@ -0,0 +1,49 @@ +fields: + - name: country_code + path: ['country', 'code'] + required: true + + - name: code # this is city code + path: ['code'] + required: true + + - name: id + path: ['id'] + + - name: country_name + path: ['country', 'name'] + + - name: name # this is CITY name + path: ['name'] + + - name: time_zone + path: ['time_zone', 'code'] + + - name: operator_id + path: ['operator_id'] + required: true + + - name: provider_name + path: ['provider', 'name'] + + - name: is_proxy_capable + path: ['is_proxy_capable'] + + - name: is_wireguard_capable + path: ['is_wireguard_capable'] + required: true + + + # required: true + + # locations.append(( + # location['country']['code'], + # location['code'], + # location['id'], + # location['country']['name'], + # location['name'], + # location['time_zone']['code'], + # location['operator_id'], + # location['provider']['name'], + # location['is_proxy_capable'], + # location['is_wireguard_capable'])) diff --git a/assets/yaml_mappings/mapping_one.yaml b/assets/yaml_mappings/mapping_one.yaml new file mode 100644 index 0000000..903eaba --- /dev/null +++ b/assets/yaml_mappings/mapping_one.yaml @@ -0,0 +1,8 @@ +fields: + - name: group_a_code + path: ['group_a', 'code'] + - name: code + path: ['code'] + - name: id + path: ['id'] + required: true diff --git a/assets/yaml_mappings/operators.yaml b/assets/yaml_mappings/operators.yaml new file mode 100644 index 0000000..f7e9b8f --- /dev/null +++ b/assets/yaml_mappings/operators.yaml @@ -0,0 +1,19 @@ +fields: + - name: id + path: ['id'] + required: true + + - name: name + path: ['name'] + + - name: public_key # this is ed25519 + path: ['public_key'] + + - name: nostr_public_key + path: ['nostr_public_key'] + + - name: nostr_profile_reference + path: ['nostr_profile_reference'] + + - name: nostr_attestation_event_reference + path: ['nostr_attestation', 'event_reference'] diff --git a/core/Constants.py b/core/Constants.py index 26d2815..5a8b5dd 100644 --- a/core/Constants.py +++ b/core/Constants.py @@ -10,7 +10,8 @@ class Constants: "TICKET_API_BASE_URL", "https://ticket.hydraveil.net" ) - SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1') + SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://dev-us-002.simplifiedprivacy.net/api/v1') + # SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1') PING_URL: Final[str] = os.environ.get('PING_URL', 'https://api.hydraveil.net/api/v1/health') CONNECTION_RETRY_INTERVAL: Final[int] = int(os.environ.get('CONNECTION_RETRY_INTERVAL', '5')) diff --git a/core/controllers/ClientController.py b/core/controllers/ClientController.py index d47eb9e..ba010dc 100644 --- a/core/controllers/ClientController.py +++ b/core/controllers/ClientController.py @@ -1,3 +1,5 @@ +from core.services.sync.sync_service import coordinate_cache_sync + from core.Constants import Constants from core.Errors import UnknownClientPathError, UnknownClientVersionError, CommandNotFoundError from core.controllers.ApplicationController import ApplicationController @@ -44,8 +46,38 @@ class ClientController: @staticmethod def sync(client_observer: ClientObserver = None, connection_observer: ConnectionObserver = None): - from core.controllers.ConnectionController import ConnectionController - ConnectionController.with_preferred_connection(task=ClientController.__sync, client_observer=client_observer, connection_observer=connection_observer) + from core.models.manage.session_management import init_session, close_session + init_session() + print("I sync'ed") + + # Use Cached Method: + changed_tables = coordinate_cache_sync(ClientObserver, ConnectionObserver) + + # SPOOFING + # changed_tables = ['version', 'applications', 'application_versions', 'client_version', 'operators', 'locations', 'subscriptions'] + + print(f"changed_tables: {changed_tables}") + + # this should be same or any errors: + if isinstance(changed_tables, dict): + # check result is "same"? + # or an errro + if client_observer is not None: + client_observer.notify('synchronized') + + # now it's likely to have results: + if isinstance(changed_tables, list): + quantity_of_changes = len(changed_tables) + + print(f"quantity_of_changes is {quantity_of_changes}") + + # sanity check: + if quantity_of_changes >= 0: + from core.controllers.ConnectionController import ConnectionController + ConnectionController.with_preferred_connection(task=ClientController.__sync, changed_tables=changed_tables, client_observer=client_observer, connection_observer=connection_observer) + else: + if client_observer is not None: + client_observer.notify('synchronized') @staticmethod def update(client_observer: ClientObserver = None, connection_observer: ConnectionObserver = None): @@ -61,24 +93,46 @@ class ClientController: return path + # def cached_sync(client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): + @staticmethod - def __sync(client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): + def __sync(changed_tables: list, client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): + + # SPOOFING + # changed_tables = ['version', 'applications', 'application_versions', 'client_version', 'operators', 'locations', 'subscriptions'] if client_observer is not None: client_observer.notify('synchronizing') - # noinspection PyProtectedMember - ApplicationController._sync(proxies=proxies) - # noinspection PyProtectedMember - ApplicationVersionController._sync(proxies=proxies) - # noinspection PyProtectedMember - ClientVersionController._sync(proxies=proxies) - # noinspection PyProtectedMember - OperatorController._sync(proxies=proxies) - # noinspection PyProtectedMember - LocationController._sync(proxies=proxies) - # noinspection PyProtectedMember - SubscriptionPlanController._sync(proxies=proxies) + if "applications" in changed_tables: + print("sync applications") + # noinspection PyProtectedMember + ApplicationController._sync(proxies=proxies) + + if "application_versions" in changed_tables: + print("sync application_versions") + # noinspection PyProtectedMember + ApplicationVersionController._sync(proxies=proxies) + + if "client_version" in changed_tables: + print("sync client_version") + # noinspection PyProtectedMember + ClientVersionController._sync(proxies=proxies) + + if "operators" in changed_tables: + print("sync operators") + # noinspection PyProtectedMember + OperatorController._sync(proxies=proxies) + + if "locations" in changed_tables: + print("sync locations") + # noinspection PyProtectedMember + LocationController._sync(proxies=proxies) + + if "subscriptions" in changed_tables: + print("sync subscriptions") + # noinspection PyProtectedMember + SubscriptionPlanController._sync(proxies=proxies) ConfigurationController.update_last_synced_at() @@ -86,7 +140,6 @@ class ClientController: client_observer.notify('synchronized') @staticmethod - def __update(client_observer: Optional[ClientObserver] = None, proxies: Optional[dict] = None): if ClientController.can_be_updated(): diff --git a/core/controllers/ConnectionController.py b/core/controllers/ConnectionController.py index bc380e4..f4a7686 100644 --- a/core/controllers/ConnectionController.py +++ b/core/controllers/ConnectionController.py @@ -29,6 +29,13 @@ class ConnectionController: @staticmethod def with_preferred_connection(*args, task: Callable[..., Any], connection_observer: Optional[ConnectionObserver] = None, **kwargs): + """ + This function does a task with a preferred connection type. + + It is currently being CALLED UPON with keyword based kwargs only, because any args would execute immediately. + + However, the *args keeps it open to future uses. + """ connection = ConfigurationController.get_connection() diff --git a/core/models/manage/get_from_model.py b/core/models/manage/get_from_model.py new file mode 100644 index 0000000..88bfaf4 --- /dev/null +++ b/core/models/manage/get_from_model.py @@ -0,0 +1,35 @@ +from core.models.manage.session_management import get_session +from typing import Type, List, Dict, Any +from sqlalchemy.exc import SQLAlchemyError + +def get_from_model(model_class: Type, **filters) -> List[Dict[str, Any]]: + """ + Retrieve data from a model. + + Args: + model_class: The ORM model class + **filters: Optional column=value pairs to filter by (e.g., id=1, name='John') + + Returns: + List of dictionaries representing matching rows + """ + with get_session() as session: + try: + query = session.query(model_class) + + # Apply filters dynamically + if filters: + for column, value in filters.items(): + if hasattr(model_class, column): + query = query.filter(getattr(model_class, column) == value) + + # Convert to list of dicts + results = [ + {c.name: getattr(row, c.name) for c in row.__table__.columns} + for row in query.all() + ] + return results + + except SQLAlchemyError as e: + raise ValueError(f"Query failed: {e}") + diff --git a/core/models/manage/insert.py b/core/models/manage/insert.py new file mode 100644 index 0000000..60d72a3 --- /dev/null +++ b/core/models/manage/insert.py @@ -0,0 +1,138 @@ +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine +from typing import Type, Dict, Any +from sqlalchemy.exc import IntegrityError, SQLAlchemyError +from core.models.python_models.Base import Base +from core.models.python_models.CachedSync import CachedSync +from core.models.manage.session_management import get_session + +def insert_into_model(model_class: Type, all_data: dict | list, override=False) -> bool: + """ + Generic ORM insert for any model. + + Args: + model_class: The ORM model class + all_data: Dictionary or list of dictionaries with data matching the model + override: If True, wipe the table before inserting + + Returns: + True if successful + """ + + print(f"all_data is {all_data}") + + with get_session() as session: + # Step 1: Wipe the table if override=True + if override: + session.query(model_class).delete() + session.commit() + + print(f"Starting insert for {model_class.__name__}") + + # Normalize to list for uniform handling + data_list = all_data if isinstance(all_data, list) else [all_data] + + try: + for each_json in data_list: + instance = model_class(**each_json) + session.add(instance) + + session.commit() + return True + + except TypeError as e: + print(f"TypeError caught: {e}") + session.rollback() + raise ValueError(f"Invalid fields for {model_class.__name__}: {e}") + except IntegrityError as e: + print(f"IntegrityError caught: {e.orig}") + session.rollback() + raise ValueError(f"Constraint violation: {e.orig}") + except SQLAlchemyError as e: + print(f"SQLAlchemyError caught: {e}") + session.rollback() + raise + except Exception as e: + print(f"Generic exception caught: {e}") + session.rollback() + raise e + + + +# def insert_into_model(model_class: Type, all_data: list, override=False) -> Any: +# """ +# Generic ORM insert for any model. But not used for the legacy codebase yet. + +# Args: +# model_class: The ORM model class +# json_data: Dictionary with data matching the model + +# Returns: +# The created instance +# """ + +# print(f"all_data is {all_data}") + +# with get_session() as session: +# # Step 1: Wipe the table if override=True +# if override: +# session.query(model_class).delete() +# session.commit() + +# print("we made it past getting the session") + +# print(f"Starting insert for {model_class.__name__}") + +# type_of_data = type(all_data) +# print(f"all_data type is: {type_of_data}") + +# if isinstance(all_data, dict): +# try: +# instance = model_class(**all_data) +# session.add(instance) +# session.commit() +# except TypeError as e: +# print(f"TypeError caught: {e}") +# session.rollback() +# raise ValueError(f"Invalid fields for {model_class.__name__}: {e}") +# except IntegrityError as e: +# print(f"IntegrityError caught: {e.orig}") +# session.rollback() +# raise ValueError(f"Constraint violation: {e.orig}") +# except SQLAlchemyError as e: +# print(f"SQLAlchemyError caught: {e}") +# session.rollback() +# raise +# except Exception as e: +# print(f"Generic exception caught: {e}") +# session.rollback() +# raise e +# return True + +# elif isinstance(all_data, list): +# for each_json in all_data: +# try: +# instance = model_class(**each_json) +# session.add(instance) +# session.commit() +# except TypeError as e: +# print(f"TypeError caught: {e}") +# session.rollback() +# raise ValueError(f"Invalid fields for {model_class.__name__}: {e}") +# except IntegrityError as e: +# print(f"IntegrityError caught: {e.orig}") +# session.rollback() +# raise ValueError(f"Constraint violation: {e.orig}") +# except SQLAlchemyError as e: +# print(f"SQLAlchemyError caught: {e}") +# session.rollback() +# raise +# except Exception as e: +# print(f"Generic exception caught: {e}") +# session.rollback() +# raise e +# return True + +# else: +# print("invalid data type") +# return False \ No newline at end of file diff --git a/core/models/manage/manae_session.py b/core/models/manage/manae_session.py new file mode 100644 index 0000000..13ce730 --- /dev/null +++ b/core/models/manage/manae_session.py @@ -0,0 +1,33 @@ +# db.py +from core.models.python_models.Base import Base +from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.exc import SQLAlchemyError + +# Create engine +try: + engine = create_engine('sqlite:///data.db', echo=False) +except Exception as e: + print(f"Failed to create engine: {e}") + raise + +Session = scoped_session(sessionmaker(bind=engine)) + +def init_session(): + """Initialize database tables.""" + try: + Base.metadata.create_all(engine) + except SQLAlchemyError as e: + print(f"Failed to initialize database: {e}") + raise + +def get_session(): + """Get the thread-local session.""" + return Session() + +def close_session(): + """Clean up the thread-local session.""" + try: + Session.remove() + except Exception as e: + print(f"Error closing session: {e}") diff --git a/core/models/manage/session_management.py b/core/models/manage/session_management.py new file mode 100644 index 0000000..ee30706 --- /dev/null +++ b/core/models/manage/session_management.py @@ -0,0 +1,27 @@ +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine +from core.models.python_models.Base import Base + +engine = create_engine("sqlite:///data.db") +Session = sessionmaker(bind=engine) + +_session = None + +def init_session(): + """Call this once at application startup""" + global _session + Base.metadata.create_all(engine) + _session = Session() + +def get_session(): + """Get the persistent session""" + if _session is None: + raise RuntimeError("Session not initialized. Call init_session() first.") + return _session + +def close_session(): + """Call this when shutting down the application""" + global _session + if _session: + _session.close() + _session = None diff --git a/core/models/manage/setup.py b/core/models/manage/setup.py new file mode 100644 index 0000000..b461c0f --- /dev/null +++ b/core/models/manage/setup.py @@ -0,0 +1,19 @@ +from contextlib import contextmanager +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine + + +engine = create_engine("sqlite:///alchemy.db") +Session = sessionmaker(bind=engine) + +Base.metadata.create_all(engine) + +@contextmanager +def get_session(): + session = Session() + try: + yield session + finally: + session.close() + + diff --git a/core/models/python_models/Base.py b/core/models/python_models/Base.py new file mode 100644 index 0000000..6cb65c8 --- /dev/null +++ b/core/models/python_models/Base.py @@ -0,0 +1,4 @@ +# base.py (or models/base.py) +from sqlalchemy.orm import declarative_base + +Base = declarative_base() diff --git a/core/models/python_models/CachedSync.py b/core/models/python_models/CachedSync.py new file mode 100644 index 0000000..31d5fff --- /dev/null +++ b/core/models/python_models/CachedSync.py @@ -0,0 +1,48 @@ +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import declarative_base, mapped_column, Mapped, relationship +from typing import Optional +from sqlalchemy.orm import Mapped +from core.models.python_models.Base import Base + +class CachedSync(Base): + __tablename__ = 'cached_sync' + + # version of the cached sync itself primary key + version: Mapped[int] = mapped_column(Integer, primary_key=True) + + applications: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + application_versions: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + client_version: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + operators: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + locations: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + subscriptions: Mapped[Optional[str]] = mapped_column(Integer, nullable=True, default=None) + +# to use: +# Lookup by composite primary key +# record = session.get(Hello, (b_value)) + + + +# # from core.models.Model import Model +# from dataclasses import dataclass +# from typing import Union, Optional + +# _table_name: str = 'cached_sync' + +# _table_definition: str = """ +# 'version' int UNIQUE, +# 'applications' int, +# 'application_versions' int, +# 'operators' int, +# 'locations' int, +# 'subscriptions' int, +# """ + +# @dataclass +# class CachedSync(Model): +# version: int +# applications: int +# application_versions: int +# operators: int +# locations: int +# subscriptions: int diff --git a/core/services/WebServiceApiService.py b/core/services/WebServiceApiService.py index b216915..55ea9ad 100644 --- a/core/services/WebServiceApiService.py +++ b/core/services/WebServiceApiService.py @@ -212,3 +212,15 @@ class WebServiceApiService: headers = None return requests.post(Constants.SP_API_BASE_URL + path, headers=headers, json=body, proxies=proxies) + + @staticmethod + def get_cached_sync(proxies: Optional[dict] = None): + + from requests.status_codes import codes as status_codes + + response = WebServiceApiService.__get('/cachedsync', None, proxies) + + if response.status_code == status_codes.OK: + return response.json() + else: + return None \ No newline at end of file diff --git a/core/services/networking/make_url.py b/core/services/networking/make_url.py index 802d9d8..6585420 100644 --- a/core/services/networking/make_url.py +++ b/core/services/networking/make_url.py @@ -3,10 +3,10 @@ from core.Constants import Constants # Prepare full endpoint urls: def make_url(which_endpoint: str) -> str: - domain = Constants.TICKET_API_BASE_URL + # domain = Constants.TICKET_API_BASE_URL # hardcode option: - # domain = "https://ticket.hydraveil.net" + domain = 'https://dev-us-002.simplifiedprivacy.net/api/v1' final_url = f"{domain}/{which_endpoint}" return final_url diff --git a/core/services/networking/regular_get_request.py b/core/services/networking/regular_get_request.py index 1361a04..a9d8e17 100644 --- a/core/services/networking/regular_get_request.py +++ b/core/services/networking/regular_get_request.py @@ -15,6 +15,7 @@ def regular_get_request(url: str, connection_observer: ConnectionObserver): import requests try: + print(f"we are doing a regular GET request to: {url}") response = requests.get(url) # Raises HTTPError for 4xx/5xx diff --git a/core/services/sync/compare_tables.py b/core/services/sync/compare_tables.py new file mode 100644 index 0000000..3ed2189 --- /dev/null +++ b/core/services/sync/compare_tables.py @@ -0,0 +1,69 @@ + +def compare_tables(new_data: dict, old_table: dict) -> tuple: + + type_of_new = type(new_data) + type_of_old = type(old_table) + print(type_of_new) + print(type_of_old) + + # ============== FILTER ================== + new_is_dictionary = isinstance(new_data, dict) + old_is_dictionary = isinstance(old_table, dict) + if new_is_dictionary and old_is_dictionary: + print("Filter Passed. Both things are dictionaries.") + else: + return None, None + + # ============== PREPARE ================== + + # what are the tables? + new_keys = new_data.keys() + old_keys = old_table.keys() + + print(f"new_keys is {new_keys}") + print(f"old_keys is {old_keys}") + + changed_tables = [] + new_data_types = [] + + # ============== EVALUATE ================== + + for new_key in new_keys: + print(f"This iteration's new_key is {new_key}") + each_value = new_data[new_key] + print(f"This value is {each_value}") + + # is the VALUE we're evaluating even a number? + if not isinstance(each_value, int): + print("skipping, it's not an int") + continue + + # do we even know about this new table category? + if new_key not in old_keys: + new_data_types.append(new_key) + continue + + # we know about it, let's see if it's new: + if each_value > old_table[new_key]: + changed_tables.append(new_key) + + return changed_tables, new_data_types + +# ============== TESTING ================== +# ISOLATION TEST: +""" +new_data = { + "a": 1, + "b": 2, + "c": 4, + "d": 1 +} +old_data = { + "a": 1, + "b": 2, + "c": 3, +} +changed_tables, new_data_types = compare_tables(new_data, old_data) +print(f"changed tables: {changed_tables}") +print(f"new data types: {new_data_types}") +""" \ No newline at end of file diff --git a/core/services/sync/sync_service.py b/core/services/sync/sync_service.py new file mode 100644 index 0000000..b323034 --- /dev/null +++ b/core/services/sync/sync_service.py @@ -0,0 +1,184 @@ +# from __future__ import annotations +# from typing import TYPE_CHECKING + +# if TYPE_CHECKING: +# from core.essentials.observers.ConnectionObserver import ConnectionObserver + # from core.observers.TicketObserver import TicketObserver + +# comparisons: +from core.services.sync.compare_tables import compare_tables + +# unique database: +from core.models.manage.session_management import get_session +from core.models.manage.get_from_model import get_from_model +from core.models.manage.insert import insert_into_model +from core.models.python_models.CachedSync import CachedSync + +from core.Constants import Constants +from core.observers.BaseObserver import BaseObserver +from core.services.networking.get_data_from_server import get_data_from_server +from core.errors.logger import logger +from core.observers.ClientObserver import ClientObserver +from core.observers.ConnectionObserver import ConnectionObserver + +# generic +from sqlalchemy import func +from typing import Optional + +# Highest Level in this Flow: +def coordinate_cache_sync( + client_observer: Optional[ClientObserver] = None, + connection_observer: Optional[ConnectionObserver] = None +) -> dict: + + # ==================== GET & VALIDATE DATA ============================ + + # get data from the endpoint: + new_data_payload = get_sync_cache_from_api(ClientObserver, ConnectionObserver) + + print("") + print(f"We have new data: {new_data_payload}") + print(type(new_data_payload)) + print("") + + # is is a dictionary? + if not isinstance(new_data_payload, dict): + return {"valid": False, "error_msg": "invalid_format_reply"} + + if "data" not in new_data_payload: + return new_data_payload + + new_data = new_data_payload.get("data", None) + + # SPOOFING: + # new_data = { + # "version": 1, + # "applications": 1, + # "application_versions": 1, + # "client_version": 1, + # "operators": 1, + # "locations": 1, + # "subscriptions": 1 + # } + + if "version" not in new_data: + return new_data # it should have the error in there. + + new_highest = new_data.get("version", None) + + print(f"new highest version is {new_highest}") + + if not new_highest: # version is empty + return new_data # it should have the error in there. + + # ==================== INITIAL SQL SESSION ===================== + session = get_session() + + # Get the highest version already saved: + previous_highest = session.query(func.max(CachedSync.version)).scalar() + print(f"previous_highest is {previous_highest}") + + # if this is the first sync, we have no data to compare it to, + if previous_highest is None: + print("No previous previous_highest or Previous data. Saving..") + + # so save the data, + saved = save_data(new_data) + if saved: + print("Saved!") + + # then the "changed" keys are all of the new ones, + changed_tables_keys = new_data.keys() + changed_tables = list(changed_tables_keys) + return changed_tables + + # ==================== EVALUATE DATA ===================== + + print(f"previous_highest is {previous_highest}") + print(f"new_highest is {new_highest}") + + # bail on the rest of this function if it's the same: + if previous_highest >= new_highest: + return {"result": "same"} + else: + print("The data is new! We now evaluate...") + + # ==================== COMPARE DATA ============================ + + # we are only doing this if we actually have old data to compare it to. + query = {"version": previous_highest} + old_data_as_list = get_from_model(CachedSync, **query) + + # if it's a list, get it out of there, + old_data = old_data_as_list[0] + + print(f"old data is: {old_data}") + + if old_data: + print(f"We got the old data successfully. Now comparing..") + + # let's see which tables are NEW: + changed_tables, new_data_types = compare_tables(new_data, old_data) + + print("") + print(f"changed_tables is {changed_tables}") + print(f"new_data_types is {new_data_types}") + print("") + + if new_data_types: + quantity_of_new_types = len(new_data_types) + else: + # we can't fetch the OLD data due to database error, so we need to replace it all, + print("database error. add logic here for quantity_of_new_types.") + changed_tables_keys = new_data.keys() + changed_tables = list(changed_tables_keys) + quantity_of_new_types = len(changed_tables) + + # ==================== FINALLY ===================== + return changed_tables + +### Add error logging to this: +def save_data(data: dict) -> bool: + print(f"about to insert the NEW data into the database.. {data}") + try: + did_it_work = insert_into_model(CachedSync, data, True) + print(f"Did it insert? {did_it_work}") + except: + print("Error! We Failed to Insert the New Data!") + did_it_work = False + + return did_it_work + + +def get_sync_cache_from_api( + client_observer: Optional[ClientObserver] = None, + connection_observer: Optional[ConnectionObserver] = None +) -> dict: + + print("we are syncing with the API..") + + # get the base url & verify it: + rejected_list = [None, False, ""] + # base_url = Constants.SP_API_BASE_URL + base_url = 'https://dev-us-002.simplifiedprivacy.net/api/v1' # temp spoof + if base_url in rejected_list: + # notification = "Base URL is Empty, so it can't fetch prices.." + # client_observer.notify(notification) + return {"valid": False, "error_code": "invalid_url"} + + # combine with endpoint: + url = f"{base_url}/cachedsync" + + try: + print("getting data from server...") + sync_results = get_data_from_server(url, connection_observer) + + if sync_results in rejected_list: + return {"valid": False, "error_code": "sync_failed"} + + logger.debug(f"Inside the sync controller, sync_results is: {sync_results}") + except: + return {"valid": False, "error_code": "sync_failed"} + + return sync_results + diff --git a/prototype_sync.py b/prototype_sync.py new file mode 100644 index 0000000..7d14b40 --- /dev/null +++ b/prototype_sync.py @@ -0,0 +1,40 @@ +from core.controllers.ClientController import ClientController +from core.observers.ClientObserver import ClientObserver +from core.observers.ConnectionObserver import ConnectionObserver +from core.models.manage.session_management import init_session, close_session + +class PrototypeSync(): + def __init__(self, parent=None): + client_observer = ClientObserver() + connection_observer = ConnectionObserver() + client_observer.subscribe('synchronizing', lambda event: print('Synchronizing...\n')) + client_observer.subscribe('updating', lambda event: print('Updating client...')) + client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get('progress'):.2f}%', flush=True, end='\r')) + client_observer.subscribe('updated', lambda event: print('\n')) + + connection_observer.subscribe('connecting', lambda event: self.update_status( + f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...')) + + connection_observer.subscribe('tor_bootstrapping', lambda event: self.update_status( + 'Establishing Tor connection...')) + + connection_observer.subscribe('tor_bootstrap_progressing', lambda event: self.update_status( + f'Bootstrapping Tor {event.meta.get('progress'):.2f}%')) + + connection_observer.subscribe( + 'tor_bootstrapped', lambda event: self.update_status('Tor connection established.')) + + + init_session() + + print("Sync..") + ClientController.sync(client_observer=client_observer, connection_observer=connection_observer) + print("Done") + + # On app shutdown + close_session() + + + def update_status(self, text, clear=False): + if text: + print(text) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f89aa21..c12431b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sp-hydra-veil-core" -version = "2.3.4" +version = "2.3.5" authors = [ { name = "Simplified Privacy" }, ]