Working python proxy/filter for room list

This commit is contained in:
miruka 2020-05-06 01:49:25 -04:00
parent eee198b238
commit 5432958121
7 changed files with 101 additions and 61 deletions

View File

@ -7,16 +7,20 @@ import traceback
from pathlib import Path from pathlib import Path
from typing import Any, DefaultDict, Dict, List, Optional from typing import Any, DefaultDict, Dict, List, Optional
import nio
from appdirs import AppDirs from appdirs import AppDirs
import nio
from . import __app_name__ from . import __app_name__
from .errors import MatrixError from .errors import MatrixError
from .matrix_client import MatrixClient from .matrix_client import MatrixClient
from .media_cache import MediaCache from .media_cache import MediaCache
from .models import SyncId from .models import SyncId
from .models.filters import FieldSubstringFilter
from .models.items import Account from .models.items import Account
from .models.model import Model
from .models.model_store import ModelStore from .models.model_store import ModelStore
from .models.special_models import AllRooms
from .user_files import Accounts, History, Theme, UISettings, UIState from .user_files import Accounts, History, Theme, UISettings, UIState
# Logging configuration # Logging configuration
@ -77,6 +81,7 @@ class Backend:
self.ui_state: UIState = UIState(self) self.ui_state: UIState = UIState(self)
self.history: History = History(self) self.history: History = History(self)
self.all_rooms: AllRooms = AllRooms()
self.models: ModelStore = ModelStore() self.models: ModelStore = ModelStore()
self.clients: Dict[str, MatrixClient] = {} self.clients: Dict[str, MatrixClient] = {}
@ -304,3 +309,12 @@ class Backend:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
failures += 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

View File

@ -871,7 +871,6 @@ class MatrixClient(nio.AsyncClient):
""" """
self.models[self.user_id, "rooms"].pop(room_id, None) 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, "events"), None)
self.models.pop((self.user_id, room_id, "members"), None) self.models.pop((self.user_id, room_id, "members"), None)
@ -1249,7 +1248,6 @@ class MatrixClient(nio.AsyncClient):
) )
self.models[self.user_id, "rooms"][room.room_id] = room_item self.models[self.user_id, "rooms"][room.room_id] = room_item
self.models["every_room"][self.user_id, room.room_id] = room_item
# List members that left the room, then remove them from our model # List members that left the room, then remove them from our model
left_the_room = [ left_the_room = [

View File

@ -1,6 +1,6 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # 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 . import SyncId
from .model import Model from .model import Model
@ -20,10 +20,20 @@ class ModelFilter(ModelProxy):
return True 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): if self.accept_source(source):
dct = self if self.accept_item(value) else self.filtered_out if self.accept_item(value):
dct[source.sync_id, key] = 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: def source_item_deleted(self, source: Model, key) -> None:
@ -64,11 +74,11 @@ class ModelFilter(ModelProxy):
class FieldSubstringFilter(ModelFilter): 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.fields: Collection[str] = fields
self._filter: str = "" self._filter: str = ""
super().__init__(*args, **kwargs) super().__init__(sync_id)
@property @property

View File

@ -40,6 +40,11 @@ class Model(MutableMapping):
self._sorted_data: List["ModelItem"] = blist() self._sorted_data: List["ModelItem"] = blist()
self._write_lock: RLock = RLock() 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: def __repr__(self) -> str:
"""Provide a full representation of the model and its content.""" """Provide a full representation of the model and its content."""
@ -63,27 +68,34 @@ class Model(MutableMapping):
return self._data[key] 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: with self._write_lock:
existing = self._data.get(key) existing = self._data.get(key)
new = value new = value
# Collect changed fields # Collect changed fields
changed_fields = {} changed_fields = _changed_fields or {}
if not changed_fields:
for field in new.__dataclass_fields__: # type: ignore for field in new.__dataclass_fields__: # type: ignore
changed = True changed = True
if existing: if existing:
changed = getattr(new, field) != getattr(existing, field) changed = \
getattr(new, field) != getattr(existing, field)
if changed: if changed:
changed_fields[field] = new.serialize_field(field) changed_fields[field] = new.serialize_field(field)
# Set parent model on new item # Set parent model on new item
if self.sync_id: if self.sync_id and self.take_items_ownership:
new.parent_model = self new.parent_model = self
# Insert into sorted data # Insert into sorted data
@ -101,6 +113,12 @@ class Model(MutableMapping):
self._data[key] = new 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 # Emit PyOtherSide event
if self.sync_id and (index_then != index_now or changed_fields): 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) index = self._sorted_data.index(item)
del self._sorted_data[index] 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: if self.sync_id:
ModelItemDeleted(self.sync_id, index) ModelItemDeleted(self.sync_id, index)

View File

@ -42,19 +42,23 @@ class ModelItem:
super().__setattr__(name, value) super().__setattr__(name, value)
model = self.parent_model parent = self.parent_model
if not model.sync_id: if not parent.sync_id:
return 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)}
ModelItemSet(model.sync_id, index_then, index_now, fields) with parent._write_lock:
index_then = parent._sorted_data.index(self)
parent._sorted_data.sort()
index_now = parent._sorted_data.index(self)
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: def __delattr__(self, name: str) -> None:

View File

@ -1,6 +1,6 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # 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 . import SyncId
from .model import Model from .model import Model
@ -12,14 +12,12 @@ if TYPE_CHECKING:
class ModelProxy(Model): class ModelProxy(Model):
def __init__(self, sync_id: SyncId) -> None: def __init__(self, sync_id: SyncId) -> None:
super().__init__(sync_id) super().__init__(sync_id)
self.take_items_ownership = False
Model.proxies[sync_id] = self Model.proxies[sync_id] = self
for sync_id, model in Model.instances.items(): for sync_id, model in Model.instances.items():
if sync_id != self.sync_id and self.accept_source(model): if sync_id != self.sync_id and self.accept_source(model):
for key, item in model.items(): for key, item in model.items():
# if isinstance(model, ModelProxy):
# key = key[1]
self.source_item_set(model, key, item) self.source_item_set(model, key, item)
@ -27,9 +25,15 @@ class ModelProxy(Model):
return True 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): 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: def source_item_deleted(self, source: Model, key) -> None:

View File

@ -8,26 +8,12 @@ import "../Base"
HListView { HListView {
id: roomList 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 { delegate: Room {
id: room id: room
width: roomList.width width: roomList.width
onActivated: showRoomAtIndex(DelegateModel.filteredIndex) onActivated: showRoomAtIndex(model.index)
ListView.onAdd: ParallelAnimation {
HNumberAnimation {
target: room; property: "opacity"; from: 0; to: 1;
}
HNumberAnimation {
target: room; property: "scale"; from: 0; to: 1;
}
}
}
} }
section.property: "for_account" section.property: "for_account"
@ -39,14 +25,16 @@ HListView {
accountModel: ModelStore.get("accounts").find(section) 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: { readonly property var sectionIndice: {
const sections = {} const sections = {}
let currentUserId = null let currentUserId = null
for (let i = 0; i < model.filtered.count; i++) { for (let i = 0; i < model.count; i++) {
const userId = model.filtered.get(i).model.for_account const userId = model.get(i).for_account
if (userId !== currentUserId) { if (userId !== currentUserId) {
sections[userId] = i sections[userId] = i
@ -68,17 +56,17 @@ HListView {
function showRoomAtIndex(index=currentIndex) { function showRoomAtIndex(index=currentIndex) {
if (index === -1) index = 0 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) pageLoader.showRoom(room.for_account, room.id)
currentIndex = index currentIndex = index
} }
function showAccountRoomAtIndex(index) { function showAccountRoomAtIndex(index) {
const currentUserId = model.filtered.get( const currentUserId = model.get(
currentIndex === -1 ? 0 : currentIndex currentIndex === -1 ? 0 : currentIndex
).model.for_account ).for_account
showRoomAtIndex(sectionIndice[currentUserId] + index) showRoomAtIndex(sectionIndice[currentUserId] + index)
} }