diff --git a/src/backend/backend.py b/src/backend/backend.py index 345273af..49955cea 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -7,16 +7,20 @@ import traceback from pathlib import Path from typing import Any, DefaultDict, Dict, List, Optional -import nio from appdirs import AppDirs +import nio + from . import __app_name__ from .errors import MatrixError from .matrix_client import MatrixClient from .media_cache import MediaCache from .models import SyncId +from .models.filters import FieldSubstringFilter from .models.items import Account +from .models.model import Model from .models.model_store import ModelStore +from .models.special_models import AllRooms from .user_files import Accounts, History, Theme, UISettings, UIState # Logging configuration @@ -77,8 +81,9 @@ class Backend: self.ui_state: UIState = UIState(self) self.history: History = History(self) - self.models: ModelStore = ModelStore() - self.clients: Dict[str, MatrixClient] = {} + self.all_rooms: AllRooms = AllRooms() + self.models: ModelStore = ModelStore() + self.clients: Dict[str, MatrixClient] = {} self.profile_cache: Dict[str, nio.ProfileGetResponse] = {} self.get_profile_locks: DefaultDict[str, asyncio.Lock] = \ @@ -304,3 +309,12 @@ class Backend: await asyncio.sleep(0.1) failures += 1 + + + async def set_substring_filter(self, model_id: SyncId, value: str) -> None: + model = Model.proxies[model_id] + + if not isinstance(model, FieldSubstringFilter): + raise TypeError("model_id must point to a FieldSubstringFilter") + + model.filter = value diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 292a6d0a..a879f52c 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -871,7 +871,6 @@ class MatrixClient(nio.AsyncClient): """ self.models[self.user_id, "rooms"].pop(room_id, None) - self.models["every_room"].pop((self.user_id, room_id), None) self.models.pop((self.user_id, room_id, "events"), None) self.models.pop((self.user_id, room_id, "members"), None) @@ -1248,8 +1247,7 @@ class MatrixClient(nio.AsyncClient): unreads = unreads, ) - self.models[self.user_id, "rooms"][room.room_id] = room_item - self.models["every_room"][self.user_id, room.room_id] = room_item + self.models[self.user_id, "rooms"][room.room_id] = room_item # List members that left the room, then remove them from our model left_the_room = [ diff --git a/src/backend/models/filters.py b/src/backend/models/filters.py index 1743f68b..d10148cf 100644 --- a/src/backend/models/filters.py +++ b/src/backend/models/filters.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from typing import TYPE_CHECKING, Collection, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, Collection, Dict, Optional, Tuple from . import SyncId from .model import Model @@ -20,10 +20,20 @@ class ModelFilter(ModelProxy): return True - def source_item_set(self, source: Model, key, value: "ModelItem") -> None: + def source_item_set( + self, + source: Model, + key, + value: "ModelItem", + _changed_fields: Optional[Dict[str, Any]] = None, + ) -> None: if self.accept_source(source): - dct = self if self.accept_item(value) else self.filtered_out - dct[source.sync_id, key] = value + if self.accept_item(value): + self.__setitem__((source.sync_id, key), value, _changed_fields) + self.filtered_out.pop((source.sync_id, key), None) + else: + self.filtered_out[source.sync_id, key] = value + self.pop((source.sync_id, key), None) def source_item_deleted(self, source: Model, key) -> None: @@ -64,11 +74,11 @@ class ModelFilter(ModelProxy): class FieldSubstringFilter(ModelFilter): - def __init__(self, fields: Collection[str], *args, **kwargs) -> None: + def __init__(self, sync_id: SyncId, fields: Collection[str]) -> None: self.fields: Collection[str] = fields self._filter: str = "" - super().__init__(*args, **kwargs) + super().__init__(sync_id) @property diff --git a/src/backend/models/model.py b/src/backend/models/model.py index b2e6b99e..81bb9fac 100644 --- a/src/backend/models/model.py +++ b/src/backend/models/model.py @@ -40,6 +40,11 @@ class Model(MutableMapping): self._sorted_data: List["ModelItem"] = blist() self._write_lock: RLock = RLock() + self.take_items_ownership: bool = True + + if self.sync_id: + self.instances[self.sync_id] = self + def __repr__(self) -> str: """Provide a full representation of the model and its content.""" @@ -63,27 +68,34 @@ class Model(MutableMapping): return self._data[key] - def __setitem__(self, key, value: "ModelItem") -> None: + def __setitem__( + self, + key, + value: "ModelItem", + _changed_fields: Optional[Dict[str, Any]] = None, + ) -> None: with self._write_lock: existing = self._data.get(key) new = value # Collect changed fields - changed_fields = {} + changed_fields = _changed_fields or {} - for field in new.__dataclass_fields__: # type: ignore - changed = True + if not changed_fields: + for field in new.__dataclass_fields__: # type: ignore + changed = True - if existing: - changed = getattr(new, field) != getattr(existing, field) + if existing: + changed = \ + getattr(new, field) != getattr(existing, field) - if changed: - changed_fields[field] = new.serialize_field(field) + if changed: + changed_fields[field] = new.serialize_field(field) # Set parent model on new item - if self.sync_id: + if self.sync_id and self.take_items_ownership: new.parent_model = self # Insert into sorted data @@ -101,6 +113,12 @@ class Model(MutableMapping): self._data[key] = new + # Callbacks + + for sync_id, proxy in self.proxies.items(): + if sync_id != self.sync_id: + proxy.source_item_set(self, key, value) + # Emit PyOtherSide event if self.sync_id and (index_then != index_now or changed_fields): @@ -121,6 +139,10 @@ class Model(MutableMapping): index = self._sorted_data.index(item) del self._sorted_data[index] + for sync_id, proxy in self.proxies.items(): + if sync_id != self.sync_id: + proxy.source_item_deleted(self, key) + if self.sync_id: ModelItemDeleted(self.sync_id, index) diff --git a/src/backend/models/model_item.py b/src/backend/models/model_item.py index 62639a2f..e65249cf 100644 --- a/src/backend/models/model_item.py +++ b/src/backend/models/model_item.py @@ -42,19 +42,23 @@ class ModelItem: super().__setattr__(name, value) - model = self.parent_model + parent = self.parent_model - if not model.sync_id: + if not parent.sync_id: return - with model._write_lock: - index_then = model._sorted_data.index(self) - model._sorted_data.sort() - index_now = model._sorted_data.index(self) + fields = {name: self.serialize_field(name)} - fields = {name: self.serialize_field(name)} + with parent._write_lock: + index_then = parent._sorted_data.index(self) + parent._sorted_data.sort() + index_now = parent._sorted_data.index(self) - ModelItemSet(model.sync_id, index_then, index_now, fields) + ModelItemSet(parent.sync_id, index_then, index_now, fields) + + for sync_id, proxy in parent.proxies.items(): + if sync_id != parent.sync_id: + proxy.source_item_set(parent, self.id, self, fields) def __delattr__(self, name: str) -> None: diff --git a/src/backend/models/proxy.py b/src/backend/models/proxy.py index 9597f5a6..5e0c0c8b 100644 --- a/src/backend/models/proxy.py +++ b/src/backend/models/proxy.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, Optional from . import SyncId from .model import Model @@ -12,14 +12,12 @@ if TYPE_CHECKING: class ModelProxy(Model): def __init__(self, sync_id: SyncId) -> None: super().__init__(sync_id) + self.take_items_ownership = False 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) @@ -27,9 +25,15 @@ class ModelProxy(Model): return True - def source_item_set(self, source: Model, key, value: "ModelItem") -> None: + def source_item_set( + self, + source: Model, + key, + value: "ModelItem", + _changed_fields: Optional[Dict[str, Any]] = None, + ) -> None: if self.accept_source(source): - self[source.sync_id, key] = value + self.__setitem__((source.sync_id, key), value, _changed_fields) def source_item_deleted(self, source: Model, key) -> None: diff --git a/src/gui/MainPane/RoomList.qml b/src/gui/MainPane/RoomList.qml index 7733a6ba..3010c862 100644 --- a/src/gui/MainPane/RoomList.qml +++ b/src/gui/MainPane/RoomList.qml @@ -8,26 +8,12 @@ import "../Base" HListView { id: roomList - add: null // See the XXX comment in HListView.qml + model: ModelStore.get("all_rooms") - model: HStringFilterModel { - id: filterModel - sourceModel: ModelStore.get("every_room") - field: "display_name" - - delegate: Room { - id: room - width: roomList.width - onActivated: showRoomAtIndex(DelegateModel.filteredIndex) - ListView.onAdd: ParallelAnimation { - HNumberAnimation { - target: room; property: "opacity"; from: 0; to: 1; - } - HNumberAnimation { - target: room; property: "scale"; from: 0; to: 1; - } - } - } + delegate: Room { + id: room + width: roomList.width + onActivated: showRoomAtIndex(model.index) } section.property: "for_account" @@ -39,14 +25,16 @@ HListView { accountModel: ModelStore.get("accounts").find(section) } + onFilterChanged: py.callCoro("set_substring_filter", ["all_rooms", filter]) - property alias filter: filterModel.filter + + property string filter: "" readonly property var sectionIndice: { const sections = {} let currentUserId = null - for (let i = 0; i < model.filtered.count; i++) { - const userId = model.filtered.get(i).model.for_account + for (let i = 0; i < model.count; i++) { + const userId = model.get(i).for_account if (userId !== currentUserId) { sections[userId] = i @@ -68,17 +56,17 @@ HListView { function showRoomAtIndex(index=currentIndex) { if (index === -1) index = 0 - index = Math.min(index, model.filtered.count - 1) + index = Math.min(index, model.count - 1) - const room = model.filtered.get(index).model + const room = model.get(index) pageLoader.showRoom(room.for_account, room.id) currentIndex = index } function showAccountRoomAtIndex(index) { - const currentUserId = model.filtered.get( + const currentUserId = model.get( currentIndex === -1 ? 0 : currentIndex - ).model.for_account + ).for_account showRoomAtIndex(sectionIndice[currentUserId] + index) }