Skip to content

Commit 96b71b0

Browse files
committed
complete refactoring of runtime + ui (MVC, qt signals, qt threads, rules management)
1 parent e15221d commit 96b71b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+6997
-3246
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import TYPE_CHECKING, Protocol
2+
3+
if TYPE_CHECKING:
4+
from PyQt6 import QtGui, QtWidgets
5+
6+
7+
class RuleOperatorLike(Protocol):
8+
operand: str
9+
data: str | None
10+
list: list["RuleOperatorLike"]
11+
12+
13+
class RuleLike(Protocol):
14+
name: str | None
15+
enabled: bool
16+
operator: RuleOperatorLike
17+
18+
19+
class StatsDialogProto(Protocol):
20+
"""Typed subset of StatsDialog used by the plugin.
21+
22+
actionsButton is injected by uic from stats.ui and otherwise appears
23+
as unknown to static analyzers.
24+
"""
25+
26+
actionsButton: "QtWidgets.QPushButton"
27+
28+
def windowIcon(self) -> "QtGui.QIcon": ...
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Runtime compatibility shim for StatsDialog across OpenSnitch versions."""
2+
3+
4+
# Runtime class kept for isinstance checks.
5+
try:
6+
from opensnitch.dialogs.events import StatsDialog
7+
except ImportError:
8+
from opensnitch.dialogs.stats import StatsDialog # type: ignore[assignment]
9+
10+
__all__ = ["StatsDialog"]

ui/opensnitch/plugins/list_subscriptions/list_subscriptions.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import threading
55
import shutil
66
import sys
7-
from typing import Any, ClassVar, Final
7+
from typing import TYPE_CHECKING, Any, ClassVar, Final, cast
88
from abc import ABCMeta
99
from datetime import datetime, timedelta
1010
from queue import Queue
1111
import requests
1212

13-
if "PyQt6" in sys.modules:
13+
if TYPE_CHECKING:
14+
from PyQt6 import QtCore, QtGui, QtWidgets
15+
elif "PyQt6" in sys.modules:
1416
from PyQt6 import QtCore, QtGui, QtWidgets
1517
elif "PyQt5" in sys.modules:
1618
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -20,10 +22,12 @@
2022
except Exception:
2123
from PyQt5 import QtCore, QtGui, QtWidgets
2224

23-
try:
24-
from opensnitch.dialogs.events import StatsDialog
25-
except ImportError:
26-
from opensnitch.dialogs.stats import StatsDialog # type: ignore
25+
from opensnitch.plugins.list_subscriptions._compat import StatsDialog
26+
from opensnitch.plugins.list_subscriptions._annotations import StatsDialogProto
27+
if TYPE_CHECKING:
28+
from opensnitch.plugins.list_subscriptions.ui.views.list_subscriptions_dialog import (
29+
ListSubscriptionsDialog,
30+
)
2731

2832
from opensnitch.plugins.list_subscriptions.io.lock import FileLock
2933
from opensnitch.plugins.list_subscriptions.io.storage import read_json_locked
@@ -34,7 +38,6 @@
3438
)
3539
from opensnitch.plugins.list_subscriptions.models.metadata import ListMetadata
3640
from opensnitch.plugins.list_subscriptions.models.subscriptions import SubscriptionSpec
37-
from opensnitch.proto import ui_pb2
3841
from opensnitch.config import Config
3942
from opensnitch.nodes import Nodes
4043
from opensnitch.notifications import DesktopNotifications
@@ -57,6 +60,8 @@
5760
from opensnitch.plugins.list_subscriptions.io.storage import (
5861
write_json_atomic_locked,
5962
)
63+
from opensnitch.proto import ui_pb2 as ui_pb2
64+
6065

6166
ch: Final[logging.StreamHandler] = logging.StreamHandler()
6267
# ch.setLevel(logging.ERROR)
@@ -139,8 +144,8 @@ def __init__(self, config: dict[str, Any] | None = None):
139144
self._app_icon = os.path.join(
140145
os.path.abspath(os.path.dirname(__file__)), "../../res/icon-white.svg"
141146
)
142-
self._cfg_dialog: Any = None
143-
self._cfg_action: Any = None
147+
self._cfg_dialog: ListSubscriptionsDialog | None = None
148+
self._cfg_action: QtGui.QAction | None = None
144149
self._cfg_toolbar_button: QtWidgets.QPushButton | None = None
145150
self.scheduled_tasks = {}
146151
self._startup_recheck_lock = threading.Lock()
@@ -583,7 +588,7 @@ def _reload_rules_for_updated_subscription(self, sub: SubscriptionSpec):
583588
continue
584589
matched = False
585590
while records.next():
586-
rule = Rule.new_from_records(records)
591+
rule = cast(ui_pb2.Rule, Rule.new_from_records(records))
587592
if rule.operator.operand == Config.OPERAND_LIST_DOMAINS:
588593
direct_dir = os.path.normpath(
589594
str(rule.operator.data or "").strip()
@@ -603,8 +608,8 @@ def _reload_rules_for_updated_subscription(self, sub: SubscriptionSpec):
603608
if not matched:
604609
continue
605610

606-
notification = ui_pb2.Notification( # type: ignore
607-
type=ui_pb2.CHANGE_RULE, # type: ignore
611+
notification = ui_pb2.Notification(
612+
type=ui_pb2.CHANGE_RULE,
608613
rules=[rule],
609614
)
610615
self._nodes.send_notification(addr, notification, None)
@@ -633,7 +638,7 @@ def _sub_key(self, sub: SubscriptionSpec):
633638
base = f"{sub.url}|{sub.filename}"
634639
return hashlib.sha1(base.encode("utf-8")).hexdigest()[:16]
635640

636-
def configure(self, parent: Any = None):
641+
def configure(self, parent: StatsDialogProto | None = None):
637642
if isinstance(parent, StatsDialog):
638643
if self._cfg_action is not None or self._cfg_toolbar_button is not None:
639644
return
@@ -649,7 +654,7 @@ def configure(self, parent: Any = None):
649654
return
650655
self._install_menu_action(parent, icon)
651656

652-
def _install_toolbar_button(self, parent: StatsDialog, icon: QtGui.QIcon):
657+
def _install_toolbar_button(self, parent: StatsDialogProto, icon: QtGui.QIcon):
653658
actions_button = getattr(parent, "actionsButton", None)
654659
if not isinstance(actions_button, QtWidgets.QPushButton):
655660
return False
@@ -686,7 +691,7 @@ def _install_toolbar_button(self, parent: StatsDialog, icon: QtGui.QIcon):
686691
button.setStatusTip("Open list subscriptions")
687692
button.setFlat(True)
688693
if not icon.isNull():
689-
button.setIcon(icon) # type: ignore[arg-type]
694+
button.setIcon(icon)
690695
button.setCursor(reference_button.cursor()) # pyright: ignore[reportArgumentType]
691696
button.setFocusPolicy(reference_button.focusPolicy()) # pyright: ignore[reportArgumentType]
692697
button.setSizePolicy(reference_button.sizePolicy()) # pyright: ignore[reportArgumentType]
@@ -724,7 +729,7 @@ def _find_layout_containing_widget(
724729
return found
725730
return None
726731

727-
def _install_menu_action(self, parent: StatsDialog, icon: QtGui.QIcon):
732+
def _install_menu_action(self, parent: StatsDialogProto, icon: QtGui.QIcon):
728733
menu = parent.actionsButton.menu()
729734
if menu is None:
730735
return
@@ -745,9 +750,10 @@ def _install_menu_action(self, parent: StatsDialog, icon: QtGui.QIcon):
745750
else:
746751
self._cfg_action = menu.addAction("List subscriptions")
747752

748-
self._cfg_action.triggered.connect(lambda *_: self._open_config_dialog(parent))
753+
if self._cfg_action is not None:
754+
self._cfg_action.triggered.connect(lambda *_: self._open_config_dialog(parent))
749755

750-
def _remove_menu_action(self, parent: StatsDialog):
756+
def _remove_menu_action(self, parent: StatsDialogProto):
751757
menu = parent.actionsButton.menu()
752758
if menu is None:
753759
return
@@ -759,7 +765,7 @@ def _remove_menu_action(self, parent: StatsDialog):
759765
self._cfg_action = None
760766
break
761767

762-
def _find_quit_action(self, menu: Any):
768+
def _find_quit_action(self, menu: QtWidgets.QMenu) -> QtGui.QAction | None:
763769
qt_key = getattr(getattr(QtCore, "Qt", object()), "Key", None)
764770
key_q = getattr(qt_key, "Key_Q", None) if qt_key is not None else None
765771
for act in menu.actions():
@@ -781,8 +787,10 @@ def _find_quit_action(self, menu: Any):
781787
return acts[-1]
782788
return None
783789

784-
def _open_config_dialog(self, parent: Any):
785-
from opensnitch.plugins.list_subscriptions.ui.list_subscriptions_dialog import ListSubscriptionsDialog
790+
def _open_config_dialog(self, parent: StatsDialogProto):
791+
from opensnitch.plugins.list_subscriptions.ui.views.list_subscriptions_dialog import (
792+
ListSubscriptionsDialog,
793+
)
786794

787795
appicon = None
788796
try:
@@ -843,7 +851,7 @@ def compile(self):
843851
self._sync_sources_dirs()
844852
self._sync_global_symlinks()
845853

846-
def run(self, parent: Any = None, args: tuple[Any, ...] = ()): # type: ignore[override]
854+
def run(self, parent: StatsDialogProto | None = None, args: tuple[Any, ...] = ()): # type: ignore[override]
847855
"""
848856
Start timers.
849857
"""
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>AttachedRulesDialog</class>
4+
<widget class="QDialog" name="AttachedRulesDialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>760</width>
10+
<height>420</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Attached rules</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="rootLayout">
17+
<property name="leftMargin">
18+
<number>12</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>12</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>12</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>12</number>
28+
</property>
29+
<property name="spacing">
30+
<number>8</number>
31+
</property>
32+
<item>
33+
<widget class="QTableWidget" name="rules_table"/>
34+
</item>
35+
<item>
36+
<layout class="QHBoxLayout" name="buttonsLayout">
37+
<item>
38+
<spacer name="horizontalSpacer">
39+
<property name="orientation">
40+
<enum>Qt::Horizontal</enum>
41+
</property>
42+
<property name="sizeHint" stdset="0">
43+
<size>
44+
<width>40</width>
45+
<height>20</height>
46+
</size>
47+
</property>
48+
</spacer>
49+
</item>
50+
<item>
51+
<widget class="QPushButton" name="create_button">
52+
<property name="text">
53+
<string>Create rule</string>
54+
</property>
55+
</widget>
56+
</item>
57+
<item>
58+
<widget class="QPushButton" name="edit_button">
59+
<property name="text">
60+
<string>Edit selected</string>
61+
</property>
62+
</widget>
63+
</item>
64+
<item>
65+
<widget class="QPushButton" name="toggle_button">
66+
<property name="text">
67+
<string>Disable</string>
68+
</property>
69+
</widget>
70+
</item>
71+
<item>
72+
<widget class="QPushButton" name="remove_button">
73+
<property name="text">
74+
<string>Remove</string>
75+
</property>
76+
</widget>
77+
</item>
78+
<item>
79+
<widget class="QPushButton" name="close_button">
80+
<property name="text">
81+
<string>Close</string>
82+
</property>
83+
</widget>
84+
</item>
85+
</layout>
86+
</item>
87+
</layout>
88+
</widget>
89+
<resources/>
90+
<connections/>
91+
</ui>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>StatusLogDialog</class>
4+
<widget class="QDialog" name="StatusLogDialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>760</width>
10+
<height>420</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Status log</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="rootLayout">
17+
<property name="leftMargin">
18+
<number>12</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>12</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>12</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>12</number>
28+
</property>
29+
<property name="spacing">
30+
<number>8</number>
31+
</property>
32+
<item>
33+
<widget class="QTextEdit" name="text_view"/>
34+
</item>
35+
<item>
36+
<layout class="QHBoxLayout" name="buttonsLayout">
37+
<item>
38+
<spacer name="horizontalSpacer">
39+
<property name="orientation">
40+
<enum>Qt::Horizontal</enum>
41+
</property>
42+
<property name="sizeHint" stdset="0">
43+
<size>
44+
<width>40</width>
45+
<height>20</height>
46+
</size>
47+
</property>
48+
</spacer>
49+
</item>
50+
<item>
51+
<widget class="QPushButton" name="copy_button">
52+
<property name="text">
53+
<string>Copy</string>
54+
</property>
55+
</widget>
56+
</item>
57+
<item>
58+
<widget class="QPushButton" name="close_button">
59+
<property name="text">
60+
<string>Close</string>
61+
</property>
62+
</widget>
63+
</item>
64+
</layout>
65+
</item>
66+
</layout>
67+
</widget>
68+
<resources/>
69+
<connections/>
70+
</ui>

0 commit comments

Comments
 (0)