diff options
Diffstat (limited to 'src/hydrilla/proxy/state_impl')
-rw-r--r-- | src/hydrilla/proxy/state_impl/_operations/prune_orphans.py | 25 | ||||
-rw-r--r-- | src/hydrilla/proxy/state_impl/base.py | 3 | ||||
-rw-r--r-- | src/hydrilla/proxy/state_impl/concrete_state.py | 22 | ||||
-rw-r--r-- | src/hydrilla/proxy/state_impl/items.py | 2 | ||||
-rw-r--r-- | src/hydrilla/proxy/state_impl/payloads.py | 170 |
5 files changed, 211 insertions, 11 deletions
diff --git a/src/hydrilla/proxy/state_impl/_operations/prune_orphans.py b/src/hydrilla/proxy/state_impl/_operations/prune_orphans.py index f4ebd52..5a3f4f0 100644 --- a/src/hydrilla/proxy/state_impl/_operations/prune_orphans.py +++ b/src/hydrilla/proxy/state_impl/_operations/prune_orphans.py @@ -102,6 +102,30 @@ WHERE file_id IN removed_files; ''' +_forget_files_data_sql = ''' +WITH forgotten_files AS ( + SELECT + f.file_id + FROM + files AS f + JOIN file_uses AS fu + USING (file_id) + LEFT JOIN item_versions AS iv + ON (fu.item_version_id = iv.item_version_id AND + iv.installed = 'I') + GROUP BY + f.file_id + HAVING + COUNT(iv.item_version_id) = 0 +) +UPDATE + files +SET + data = NULL +WHERE + file_id IN forgotten_files; +''' + _remove_repo_iterations_sql = ''' WITH removed_iterations AS ( SELECT @@ -141,5 +165,6 @@ def prune_orphans(cursor: sqlite3.Cursor) -> None: cursor.execute(sql) cursor.execute(_remove_items_sql) cursor.execute(_remove_files_sql) + cursor.execute(_forget_files_data_sql) cursor.execute(_remove_repo_iterations_sql) cursor.execute(_remove_repos_sql) diff --git a/src/hydrilla/proxy/state_impl/base.py b/src/hydrilla/proxy/state_impl/base.py index 82b8734..0559a42 100644 --- a/src/hydrilla/proxy/state_impl/base.py +++ b/src/hydrilla/proxy/state_impl/base.py @@ -35,6 +35,7 @@ from __future__ import annotations import sqlite3 import threading +import secrets import dataclasses as dc import typing as t @@ -143,6 +144,8 @@ class HaketiloStateWithFields(st.HaketiloState): #settings: st.HaketiloGlobalSettings + secret: bytes = dc.field(default_factory=(lambda: secrets.token_bytes(16))) + policy_tree: PolicyTree = PolicyTree() payloads_data: PayloadsData = dc.field(default_factory=dict) diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py index 4781baa..8bd25a9 100644 --- a/src/hydrilla/proxy/state_impl/concrete_state.py +++ b/src/hydrilla/proxy/state_impl/concrete_state.py @@ -158,12 +158,14 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): _operations.pull_missing_files(cursor) def rebuild_structures(self) -> None: - with self.cursor(transaction=True) as cursor: + with self.cursor() as cursor: cursor.execute( ''' SELECT - p.payload_id, p.pattern, p.eval_allowed, - p.cors_bypass_allowed, + p.payload_id, + p.pattern, + p.eval_allowed, + p.cors_bypass_allowed, ms.enabled, i.identifier FROM @@ -175,7 +177,7 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): ''' ) - rows = cursor.fetchall() + rows = cursor.fetchall() new_policy_tree = base.PolicyTree() @@ -214,12 +216,13 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): pattern_path_segments = parsed_pattern.path_segments payload_data = st.PayloadData( - payload_ref = payload_ref, + ref = payload_ref, explicitly_enabled = enabled_status == 'E', unique_token = token, pattern_path_segments = pattern_path_segments, eval_allowed = eval_allowed, - cors_bypass_allowed = cors_bypass_allowed + cors_bypass_allowed = cors_bypass_allowed, + global_secret = self.secret ) new_payloads_data[payload_ref] = payload_data @@ -245,8 +248,11 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): def resource_version_store(self) -> st.ResourceVersionStore: return items.ConcreteResourceVersionStore(self) - def get_payload(self, payload_id: str) -> st.PayloadRef: - raise NotImplementedError() + def payload_store(self) -> st.PayloadStore: + return payloads.ConcretePayloadStore(self) + + def get_secret(self) -> bytes: + return self.secret def get_settings(self) -> st.HaketiloGlobalSettings: return st.HaketiloGlobalSettings( diff --git a/src/hydrilla/proxy/state_impl/items.py b/src/hydrilla/proxy/state_impl/items.py index 5f2f274..3ba8f80 100644 --- a/src/hydrilla/proxy/state_impl/items.py +++ b/src/hydrilla/proxy/state_impl/items.py @@ -232,7 +232,7 @@ class ConcreteMappingRef(st.MappingRef): new_frozen_status == old_frozen_status): return else: - if old_active_version_id is None: + if old_active_version_id is None and old_enabled_status != 'D': return self.state.recompute_dependencies([int(self.id)]) diff --git a/src/hydrilla/proxy/state_impl/payloads.py b/src/hydrilla/proxy/state_impl/payloads.py index 2bee11f..e622e52 100644 --- a/src/hydrilla/proxy/state_impl/payloads.py +++ b/src/hydrilla/proxy/state_impl/payloads.py @@ -31,11 +31,14 @@ This module provides an interface to interact with payloads inside Haketilo. # Enable using with Python 3.7. from __future__ import annotations +import sqlite3 import dataclasses as dc import typing as t +from ... import item_infos from .. import state as st from . import base +from . import items @dc.dataclass(frozen=True, unsafe_hash=True) @@ -48,8 +51,163 @@ class ConcretePayloadRef(st.PayloadRef): except KeyError: raise st.MissingItemError() - def get_mapping(self) -> st.MappingVersionRef: - raise NotImplementedError() + def has_problems(self) -> bool: + with self.state.cursor(transaction=True) as cursor: + cursor.execute( + ''' + SELECT + iv.installed == 'F' + FROM + payloads AS p + JOIN item_versions AS iv + ON p.mapping_item_id = iv.item_version_id + WHERE + p.payload_id = ?; + ''', + (self.id,) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (mapping_install_failed,), = rows + if mapping_install_failed: + return True + + cursor.execute( + ''' + SELECT + COUNT(*) > 0 + FROM + payloads AS p + JOIN resolved_depended_resources AS rdd + USING (payload_id) + JOIN item_versions AS iv + ON rdd.resource_item_id = iv.item_version_id + WHERE + p.payload_id = ? AND iv.installed = 'F'; + ''', + (self.id,) + ) + + (resource_install_failed,), = cursor.fetchall() + if resource_install_failed: + return True + + return False + + def get_display_info(self) -> st.PayloadDisplayInfo: + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + p.pattern, + ive.item_version_id, + ive.definition, + ive.repo, + ive.repo_iteration, + ive.installed, + ive.active, + ive.is_orphan, + ive.is_local + FROM + payloads AS p + JOIN item_versions_extra AS ive + ON p.mapping_item_id = ive.item_version_id + WHERE + p.payload_id = ?; + ''', + (self.id,) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (pattern_str, mapping_version_id, definition, repo, repo_iteration, + installed_status, active_status, is_orphan, is_local), = rows + + has_problems = self.has_problems() + + mapping_version_ref = items.ConcreteMappingVersionRef( + id = str(mapping_version_id), + state = self.state + ) + + mapping_version_info = item_infos.MappingInfo.load( + definition, + repo, + repo_iteration + ) + + mapping_version_display_info = st.MappingVersionDisplayInfo( + ref = mapping_version_ref, + info = mapping_version_info, + installed = st.InstalledStatus(installed_status), + active = st.ActiveStatus(active_status), + is_orphan = is_orphan, + is_local = is_local + ) + + return st.PayloadDisplayInfo( + ref = self, + mapping_info = mapping_version_display_info, + pattern = pattern_str, + has_problems = has_problems + ) + + def ensure_items_installed(self) -> None: + with self.state.cursor(transaction=True) as cursor: + cursor.execute( + 'SELECT mapping_item_id FROM payloads WHERE payload_id = ?;', + (self.id,) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (mapping_version_id,), = rows + + cursor.execute( + ''' + UPDATE + item_versions + SET + installed = 'I' + WHERE + item_version_id = ?; + ''', + (mapping_version_id,) + ) + + cursor.execute( + ''' + WITH depended_resource_ids AS ( + SELECT + rdd.resource_item_id + FROM + payloads AS p + JOIN resolved_depended_resources AS rdd + USING (payload_id) + WHERE + payload_id = ? + ) + UPDATE + item_versions + SET + installed = 'I' + WHERE + item_version_id IN depended_resource_ids; + ''', + (self.id,) + ) + + self.state.pull_missing_files() def get_script_paths(self) \ -> t.Iterable[t.Sequence[str]]: @@ -135,3 +293,11 @@ class ConcretePayloadRef(st.PayloadRef): (data, mime_type), = result return st.FileData(type=mime_type, name=file_name, contents=data) + + +@dc.dataclass(frozen=True) +class ConcretePayloadStore(st.PayloadStore): + state: base.HaketiloStateWithFields + + def get(self, id: str) -> st.PayloadRef: + return ConcretePayloadRef(str(int(id)), self.state) |