clan-app: fixed broken webview delete_task
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import threading
|
||||
import uuid
|
||||
from contextlib import ExitStack
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
@@ -108,7 +109,13 @@ class ApiBridge(Protocol):
|
||||
timeout: Timeout in seconds when waiting for completion
|
||||
|
||||
"""
|
||||
op_key = request.op_key or "unknown"
|
||||
op_key = request.header.get("op_key", request.op_key)
|
||||
if not isinstance(op_key, str):
|
||||
msg = f"Expected op_key to be a string, got {type(op_key)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
# Validate operation key
|
||||
self._validate_operation_key(op_key)
|
||||
|
||||
def thread_task(stop_event: threading.Event) -> None:
|
||||
set_should_cancel(lambda: stop_event.is_set())
|
||||
@@ -144,3 +151,15 @@ class ApiBridge(Protocol):
|
||||
"Request timeout",
|
||||
["api_bridge", request.method_name],
|
||||
)
|
||||
|
||||
def _validate_operation_key(self, op_key: str) -> None:
|
||||
"""Validate that the operation key is valid and not in use."""
|
||||
try:
|
||||
uuid.UUID(op_key)
|
||||
except ValueError as e:
|
||||
msg = f"op_key '{op_key}' is not a valid UUID"
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if op_key in self.threads:
|
||||
msg = f"Operation key '{op_key}' is already in use. Please try again."
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -283,6 +283,29 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
|
||||
)
|
||||
return None
|
||||
|
||||
def _parse_request_data(
|
||||
self,
|
||||
request_data: dict[str, Any],
|
||||
gen_op_key: str,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], str]:
|
||||
"""Parse and validate request data components."""
|
||||
header = request_data.get("header", {})
|
||||
if not isinstance(header, dict):
|
||||
msg = f"Expected header to be a dict, got {type(header)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
body = request_data.get("body", {})
|
||||
if not isinstance(body, dict):
|
||||
msg = f"Expected body to be a dict, got {type(body)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
op_key = header.get("op_key", gen_op_key)
|
||||
if not isinstance(op_key, str):
|
||||
msg = f"Expected op_key to be a string, got {type(op_key)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
return header, body, op_key
|
||||
|
||||
def _handle_api_request(
|
||||
self,
|
||||
method_name: str,
|
||||
@@ -315,41 +338,6 @@ class HttpBridge(ApiBridge, BaseHTTPRequestHandler):
|
||||
|
||||
self._process_api_request_in_thread(api_request)
|
||||
|
||||
def _parse_request_data(
|
||||
self,
|
||||
request_data: dict[str, Any],
|
||||
gen_op_key: str,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], str]:
|
||||
"""Parse and validate request data components."""
|
||||
header = request_data.get("header", {})
|
||||
if not isinstance(header, dict):
|
||||
msg = f"Expected header to be a dict, got {type(header)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
body = request_data.get("body", {})
|
||||
if not isinstance(body, dict):
|
||||
msg = f"Expected body to be a dict, got {type(body)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
op_key = header.get("op_key", gen_op_key)
|
||||
if not isinstance(op_key, str):
|
||||
msg = f"Expected op_key to be a string, got {type(op_key)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
return header, body, op_key
|
||||
|
||||
def _validate_operation_key(self, op_key: str) -> None:
|
||||
"""Validate that the operation key is valid and not in use."""
|
||||
try:
|
||||
uuid.UUID(op_key)
|
||||
except ValueError as e:
|
||||
msg = f"op_key '{op_key}' is not a valid UUID"
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if op_key in self.threads:
|
||||
msg = f"Operation key '{op_key}' is already in use. Please try again."
|
||||
raise ValueError(msg)
|
||||
|
||||
def process_request_in_thread(
|
||||
self,
|
||||
request: BackendRequest,
|
||||
|
||||
@@ -70,9 +70,32 @@ class Webview:
|
||||
# initialized later
|
||||
_bridge: WebviewBridge | None = None
|
||||
_handle: Any | None = None
|
||||
_callbacks: dict[str, Callable[..., Any]] = field(default_factory=dict)
|
||||
__callbacks: dict[str, Callable[..., Any]] = field(default_factory=dict)
|
||||
_middleware: list["Middleware"] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def callbacks(self) -> dict[str, Callable[..., Any]]:
|
||||
return self.__callbacks
|
||||
|
||||
@callbacks.setter
|
||||
def callbacks(self, value: dict[str, Callable[..., Any]]) -> None:
|
||||
del value # Unused
|
||||
msg = "Cannot set callbacks directly"
|
||||
raise AttributeError(msg)
|
||||
|
||||
def delete_callback(self, name: str) -> None:
|
||||
if name in self.callbacks:
|
||||
del self.__callbacks[name]
|
||||
else:
|
||||
msg = f"Callback {name} does not exist. Cannot delete."
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def add_callback(self, name: str, callback: Callable[..., Any]) -> None:
|
||||
if name in self.callbacks:
|
||||
msg = f"Callback {name} already exists. Cannot add."
|
||||
raise RuntimeError(msg)
|
||||
self.__callbacks[name] = callback
|
||||
|
||||
def _create_handle(self) -> None:
|
||||
# Initialize the webview handle
|
||||
with_debugger = True
|
||||
@@ -84,12 +107,10 @@ class Webview:
|
||||
)
|
||||
else:
|
||||
handle = _webview_lib.webview_create(int(with_debugger), self.window)
|
||||
callbacks: dict[str, Callable[..., Any]] = {}
|
||||
|
||||
# Since we can't use object.__setattr__, we'll initialize differently
|
||||
# by storing in __dict__ directly (this works for init=False fields)
|
||||
self._handle = handle
|
||||
self._callbacks = callbacks
|
||||
|
||||
if self.title:
|
||||
self.set_title(self.title)
|
||||
@@ -162,6 +183,7 @@ class Webview:
|
||||
"""Create and initialize the WebviewBridge with current middleware."""
|
||||
# Use shared_threads if provided, otherwise let WebviewBridge use its default
|
||||
if self.shared_threads is not None:
|
||||
log.warning("create_bridge: Shared threads id: %s", id(self.shared_threads))
|
||||
bridge = WebviewBridge(
|
||||
webview=self,
|
||||
middleware_chain=tuple(self._middleware),
|
||||
@@ -193,7 +215,7 @@ class Webview:
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the webview."""
|
||||
for name in list(self._callbacks.keys()):
|
||||
for name in list(self.callbacks.keys()):
|
||||
self.unbind(name)
|
||||
_webview_lib.webview_terminate(self.handle)
|
||||
_webview_lib.webview_destroy(self.handle)
|
||||
@@ -217,11 +239,8 @@ class Webview:
|
||||
)
|
||||
c_callback = _webview_lib.binding_callback_t(wrapper)
|
||||
|
||||
if name in self._callbacks:
|
||||
msg = f"Callback {name} already exists. Skipping binding."
|
||||
raise RuntimeError(msg)
|
||||
self.add_callback(name, c_callback)
|
||||
|
||||
self._callbacks[name] = c_callback
|
||||
_webview_lib.webview_bind(
|
||||
self.handle,
|
||||
_encode_c_string(name),
|
||||
@@ -241,7 +260,7 @@ class Webview:
|
||||
self.return_(seq.decode(), 0 if success else 1, json.dumps(result))
|
||||
|
||||
c_callback = _webview_lib.binding_callback_t(wrapper)
|
||||
self._callbacks[name] = c_callback
|
||||
self.add_callback(name, c_callback)
|
||||
_webview_lib.webview_bind(self.handle, _encode_c_string(name), c_callback, None)
|
||||
|
||||
def get_native_handle(
|
||||
@@ -260,9 +279,9 @@ class Webview:
|
||||
return handle if handle else None
|
||||
|
||||
def unbind(self, name: str) -> None:
|
||||
if name in self._callbacks:
|
||||
if name in self.callbacks:
|
||||
_webview_lib.webview_unbind(self.handle, _encode_c_string(name))
|
||||
del self._callbacks[name]
|
||||
self.delete_callback(name)
|
||||
|
||||
def return_(self, seq: str, status: int, result: str) -> None:
|
||||
_webview_lib.webview_return(
|
||||
|
||||
@@ -46,13 +46,11 @@ class WebviewBridge(ApiBridge):
|
||||
) -> None:
|
||||
"""Handle a call from webview's JavaScript bridge."""
|
||||
try:
|
||||
op_key = op_key_bytes.decode()
|
||||
webview_op_key = op_key_bytes.decode()
|
||||
raw_args = json.loads(request_data.decode())
|
||||
|
||||
# Parse the webview-specific request format
|
||||
header = {}
|
||||
args = {}
|
||||
|
||||
if len(raw_args) == 1:
|
||||
request = raw_args[0]
|
||||
header = request.get("header", {})
|
||||
@@ -75,16 +73,14 @@ class WebviewBridge(ApiBridge):
|
||||
method_name=method_name,
|
||||
args=args,
|
||||
header=header,
|
||||
op_key=op_key,
|
||||
op_key=webview_op_key,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
msg = (
|
||||
f"Error while handling webview call {method_name} with op_key {op_key}"
|
||||
)
|
||||
msg = f"Error while handling webview call {method_name} with op_key {webview_op_key}"
|
||||
log.exception(msg)
|
||||
self.send_api_error_response(
|
||||
op_key,
|
||||
webview_op_key,
|
||||
str(e),
|
||||
["webview_bridge", method_name],
|
||||
)
|
||||
@@ -93,6 +89,6 @@ class WebviewBridge(ApiBridge):
|
||||
# Process in a separate thread using the inherited method
|
||||
self.process_request_in_thread(
|
||||
api_request,
|
||||
thread_name="WebviewThread",
|
||||
thread_name=f"WebviewThread-{method_name}",
|
||||
wait_for_completion=False,
|
||||
)
|
||||
|
||||
@@ -23,7 +23,6 @@ class ArgumentParsingMiddleware(Middleware):
|
||||
for k, v in context.request.args.items():
|
||||
# Get the expected argument type from the API
|
||||
arg_class = self.api.get_method_argtype(context.request.method_name, k)
|
||||
|
||||
# Convert dictionary to dataclass instance
|
||||
reconciled_arguments[k] = from_dict(arg_class, v)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user