From eee198b2385c5f86666408fc31cba37149cc8205 Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 6 May 2020 00:26:52 -0400 Subject: [PATCH] Add python proxy/filter models --- src/backend/models/filters.py | 102 +++++++++++++++++++++++++++ src/backend/models/model.py | 5 ++ src/backend/models/proxy.py | 44 ++++++++++++ src/backend/models/special_models.py | 17 +++++ 4 files changed, 168 insertions(+) create mode 100644 src/backend/models/filters.py create mode 100644 src/backend/models/proxy.py create mode 100644 src/backend/models/special_models.py diff --git a/src/backend/models/filters.py b/src/backend/models/filters.py new file mode 100644 index 00000000..1743f68b --- /dev/null +++ b/src/backend/models/filters.py @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later + +from typing import TYPE_CHECKING, Collection, Dict, Optional, Tuple + +from . import SyncId +from .model import Model +from .proxy import ModelProxy + +if TYPE_CHECKING: + from .model_item import ModelItem + + +class ModelFilter(ModelProxy): + def __init__(self, sync_id: SyncId) -> None: + self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {} + super().__init__(sync_id) + + + def accept_item(self, item: "ModelItem") -> bool: + return True + + + def source_item_set(self, source: Model, key, value: "ModelItem") -> None: + if self.accept_source(source): + dct = self if self.accept_item(value) else self.filtered_out + dct[source.sync_id, key] = value + + + def source_item_deleted(self, source: Model, key) -> None: + if self.accept_source(source): + try: + del self[source.sync_id, key] + except KeyError: + del self.filtered_out[source.sync_id, key] + + + def source_cleared(self, source: Model) -> None: + if self.accept_source(source): + for source_sync_id, key in self.copy(): + if source_sync_id == source.sync_id: + try: + del self[source.sync_id, key] + except KeyError: + del self.filtered_out[source.sync_id, key] + + + def refilter(self) -> None: + take_out = [] + bring_back = [] + + for key, item in self.items(): + if not self.accept_item(item): + take_out.append(key) + + for key, item in self.filtered_out.items(): + if self.accept_item(item): + bring_back.append(key) + + for key in take_out: + self.filtered_out[key] = self.pop(key) + + for key in bring_back: + self[key] = self.filtered_out.pop(key) + + +class FieldSubstringFilter(ModelFilter): + def __init__(self, fields: Collection[str], *args, **kwargs) -> None: + self.fields: Collection[str] = fields + self._filter: str = "" + + super().__init__(*args, **kwargs) + + + @property + def filter(self) -> str: + return self._filter + + + @filter.setter + def filter(self, value: str) -> None: + self._filter = value + self.refilter() + + + def accept_item(self, item: "ModelItem") -> bool: + if not self.filter: + return True + + text = " ".join((getattr(item, f) for f in self.fields)) + filt = self.filter + filt_lower = filt.lower() + + if filt_lower == filt: + # Consider case only if filter isn't all lowercase (smart case) + filt = filt_lower + text = text.lower() + + for word in filt.split(): + if word and word not in text: + return False + + return True diff --git a/src/backend/models/model.py b/src/backend/models/model.py index 68b509f0..b2e6b99e 100644 --- a/src/backend/models/model.py +++ b/src/backend/models/model.py @@ -13,6 +13,7 @@ from . import SyncId if TYPE_CHECKING: from .model_item import ModelItem + from .proxy import ModelProxy # noqa class Model(MutableMapping): @@ -29,6 +30,10 @@ class Model(MutableMapping): Items in the model are kept sorted using the `ModelItem` subclass `__lt__`. """ + instances: Dict[SyncId, "Model"] = {} + proxies: Dict[SyncId, "ModelProxy"] = {} + + def __init__(self, sync_id: Optional[SyncId]) -> None: self.sync_id: Optional[SyncId] = sync_id self._data: Dict[Any, "ModelItem"] = {} diff --git a/src/backend/models/proxy.py b/src/backend/models/proxy.py new file mode 100644 index 00000000..9597f5a6 --- /dev/null +++ b/src/backend/models/proxy.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later + +from typing import TYPE_CHECKING + +from . import SyncId +from .model import Model + +if TYPE_CHECKING: + from .model_item import ModelItem + + +class ModelProxy(Model): + def __init__(self, sync_id: SyncId) -> None: + super().__init__(sync_id) + Model.proxies[sync_id] = self + + for sync_id, model in Model.instances.items(): + if sync_id != self.sync_id and self.accept_source(model): + for key, item in model.items(): + # if isinstance(model, ModelProxy): + # key = key[1] + + self.source_item_set(model, key, item) + + + def accept_source(self, source: Model) -> bool: + return True + + + def source_item_set(self, source: Model, key, value: "ModelItem") -> None: + if self.accept_source(source): + self[source.sync_id, key] = value + + + def source_item_deleted(self, source: Model, key) -> None: + if self.accept_source(source): + del self[source.sync_id, key] + + + def source_cleared(self, source: Model) -> None: + if self.accept_source(source): + for source_sync_id, key in self.copy(): + if source_sync_id == source.sync_id: + del self[source_sync_id, key] diff --git a/src/backend/models/special_models.py b/src/backend/models/special_models.py new file mode 100644 index 00000000..01007760 --- /dev/null +++ b/src/backend/models/special_models.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later + +from .filters import FieldSubstringFilter +from .model import Model + + +class AllRooms(FieldSubstringFilter): + def __init__(self) -> None: + super().__init__(sync_id="all_rooms", fields=("display_name",)) + + + def accept_source(self, source: Model) -> bool: + return ( + isinstance(source.sync_id, tuple) and + len(source.sync_id) == 2 and + source.sync_id[1] == "rooms" # type: ignore + )