Coverage for slidge/space/space.py: 91%
92 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 12:26 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 12:26 +0000
1from collections.abc import Iterable
2from pathlib import Path
3from typing import Any
5from typing_extensions import TypedDict
7from ..contact import LegacyContact
8from ..core.mixins.avatar import ImageMixin
9from ..db.avatar import CachedAvatar
10from ..db.models import Space
11from ..util.types import AnySession, Avatar, ExtraAttrMapping
14class _SpaceProps(TypedDict, total=False):
15 name: str | None
16 description: str | None
17 member_count: int | None
18 creator_pk: int | None
21class LegacySpace[T: ExtraAttrMapping = Any](ImageMixin[T, _SpaceProps]):
22 """
23 A :xep:`0503` space.
24 """
26 _model: Space
27 _stored: Space
29 def __init__(self, session: AnySession, *, stored: Space) -> None:
30 self._session = session
31 self.log = session.log
32 self.xmpp = session.xmpp
33 self._stored = stored
34 super().__init__()
36 @property
37 def name(self) -> str | None:
38 return self._stored.name
40 @name.setter
41 def name(self, name: str | None) -> None:
42 if self._update_stored_attribute(name=name):
43 self.__broadcast_meta()
45 @property
46 def description(self) -> str | None:
47 return self._stored.description
49 @description.setter
50 def description(self, description: str | None) -> None:
51 if self._update_stored_attribute(description=description):
52 self.__broadcast_meta()
54 @property
55 def member_count(self) -> int | None:
56 return self._stored.member_count
58 @member_count.setter
59 def member_count(self, member_count: int | None) -> None:
60 if self._update_stored_attribute(member_count=member_count):
61 self.__broadcast_meta()
63 @property
64 def owners(self) -> list[LegacyContact]:
65 with self.xmpp.store.session() as orm:
66 orm.add(self._stored)
67 return [self._session.contacts._from_store(o) for o in self._stored.owners]
69 @owners.setter
70 def owners(self, owners: Iterable[LegacyContact]) -> None:
71 with self.xmpp.store.session(expire_on_commit=False) as orm:
72 orm.add(self._stored)
73 self._stored.owners = [orm.merge(o._stored) for o in owners]
74 orm.commit()
76 @property
77 def creator(self) -> LegacyContact | None:
78 self._stored.ensure_relation("creator", self.xmpp.store.session)
79 return self._session.contacts._from_store(self._stored.creator) # type:ignore[no-any-return]
81 @creator.setter
82 def creator(self, creator: LegacyContact | None) -> None:
83 with self.xmpp.store.session(expire_on_commit=False) as orm:
84 orm.add(self._stored)
85 if self._stored.creator == creator:
86 return
87 self._stored.creator = (
88 None if creator is None else orm.merge(creator._stored)
89 )
90 orm.commit()
92 @property
93 def banner(self) -> Avatar | None:
94 """
95 This property can be used to set or unset the banner.
96 """
97 stored = self._get_stored_image("banner")
98 if stored is None:
99 return None
100 return stored.to_meta()
102 @banner.setter
103 def banner(self, banner: Avatar | Path | str | None) -> None:
104 self._image_setter(banner, "banner")
106 def _post_image_update(
107 self, cached_avatar: CachedAvatar | None, *, attr: str = "avatar"
108 ) -> None:
109 if cached_avatar is not None and cached_avatar._stored.http_id is None:
110 # happens when we couldn't upload the image anywhere
111 return
112 self.__broadcast([f"urn:xmpp:spaces:{attr}:metadata:0"])
114 @property
115 def node(self) -> str:
116 return self._session.spaces.legacy_id_to_node(self.legacy_id)
118 @property
119 def legacy_id(self) -> str:
120 return self._stored.legacy_id
122 def _on_delete(self) -> None:
123 msg = self.xmpp.make_message(
124 mfrom=self.xmpp.boundjid.bare, mto=self._session.user.jid
125 )
126 msg["pubsub_event"]["delete"]["node"] = self.node
127 msg.send()
129 def __broadcast_meta(self) -> None:
130 if not self._session.rooms.is_filling:
131 self.xmpp.pubsub.broadcast_space_metadata(
132 self._session, self._stored, self.node
133 )
135 def __broadcast(self, item_ids: list[str]) -> None:
136 if not self._session.rooms.is_filling:
137 self._stored.ensure_relation("avatar", self.xmpp.store.session)
138 self._stored.ensure_relation("banner", self.xmpp.store.session)
139 self.xmpp.pubsub.broadcast_space(
140 self._session, self._stored, self.node, item_ids
141 )