Daniel Brötzmann pushed to branch fix-event-storage at gajim / gajim
Commits:
-
8e5bb40c
by wurstsalat at 2022-11-09T20:32:44+01:00
-
dfa950f3
by Philipp Hörist at 2022-11-11T19:07:12+01:00
4 changed files:
- gajim/common/preview_helpers.py
- gajim/common/storage/events.py
- gajim/gtk/control.py
- gajim/gtk/conversation/view.py
Changes:
| ... | ... | @@ -154,7 +154,12 @@ def create_thumbnail(data: bytes, |
| 154 | 154 | mime_type: str
|
| 155 | 155 | ) -> Optional[bytes]:
|
| 156 | 156 | |
| 157 | - thumbnail = create_thumbnail_with_pil(data, size)
|
|
| 157 | + try:
|
|
| 158 | + thumbnail = create_thumbnail_with_pil(data, size)
|
|
| 159 | + except (Image.DecompressionBombError, Image.DecompressionBombWarning):
|
|
| 160 | + # Don't try to process image further
|
|
| 161 | + return None
|
|
| 162 | + |
|
| 158 | 163 | if thumbnail is not None:
|
| 159 | 164 | return thumbnail
|
| 160 | 165 | return create_thumbnail_with_pixbuf(data, size, mime_type)
|
| ... | ... | @@ -210,6 +215,10 @@ def create_thumbnail_with_pil(data: bytes, size: int) -> Optional[bytes]: |
| 210 | 215 | output_file = BytesIO()
|
| 211 | 216 | try:
|
| 212 | 217 | image = Image.open(input_file)
|
| 218 | + except (Image.DecompressionBombError,
|
|
| 219 | + Image.DecompressionBombWarning) as error:
|
|
| 220 | + log.warning('Decompression bomb detected: %s', error)
|
|
| 221 | + raise
|
|
| 213 | 222 | except Exception as error:
|
| 214 | 223 | log.warning('making pil thumbnail failed: %s', error)
|
| 215 | 224 | log.warning('fallback to pixbuf')
|
| ... | ... | @@ -13,12 +13,12 @@ |
| 13 | 13 | # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
| 14 | 14 | |
| 15 | 15 | from __future__ import annotations
|
| 16 | -import dataclasses
|
|
| 17 | 16 | |
| 18 | 17 | from typing import Any
|
| 19 | -from typing import Iterator
|
|
| 20 | 18 | from typing import NamedTuple
|
| 21 | 19 | |
| 20 | +import dataclasses
|
|
| 21 | + |
|
| 22 | 22 | import json
|
| 23 | 23 | import sqlite3
|
| 24 | 24 | import logging
|
| ... | ... | @@ -116,18 +116,26 @@ def store(self, |
| 116 | 116 | def load(self,
|
| 117 | 117 | contact: ChatContactT,
|
| 118 | 118 | before: bool,
|
| 119 | - timestamp: float
|
|
| 120 | - ) -> Iterator[EventRow]:
|
|
| 119 | + timestamp: float,
|
|
| 120 | + n_lines: int,
|
|
| 121 | + ) -> list[events.ApplicationEvent]:
|
|
| 121 | 122 | |
| 122 | - time_operator = '<' if before else '>'
|
|
| 123 | + if before:
|
|
| 124 | + time_order = 'AND timestamp < ? ORDER BY timestamp DESC'
|
|
| 125 | + else:
|
|
| 126 | + time_order = 'AND timestamp > ? ORDER BY timestamp ASC'
|
|
| 123 | 127 | |
| 124 | 128 | insert_sql = '''
|
| 125 | 129 | SELECT account, jid, event, timestamp, data as "data [JSON]"
|
| 126 | - FROM events WHERE account=? AND jid=? AND timestamp %s ?
|
|
| 127 | - ''' % time_operator
|
|
| 130 | + FROM events WHERE account=? AND jid=? {time_order}
|
|
| 131 | + LIMIT ?'''.format(time_order=time_order)
|
|
| 128 | 132 | |
| 133 | + event_list: list[events.ApplicationEvent] = []
|
|
| 129 | 134 | for row in self._con.execute(insert_sql, (contact.account,
|
| 130 | 135 | contact.jid,
|
| 131 | - timestamp)).fetchall():
|
|
| 136 | + timestamp,
|
|
| 137 | + n_lines)).fetchall():
|
|
| 132 | 138 | event_class = EVENT_CLASSES[row.event]
|
| 133 | - yield event_class(**row.data, timestamp=row.timestamp) |
|
| 139 | + event_list.append(event_class(**row.data,
|
|
| 140 | + timestamp=row.timestamp))
|
|
| 141 | + return event_list |
| ... | ... | @@ -56,18 +56,18 @@ |
| 56 | 56 | from gajim.gui.groupchat_roster import GroupchatRoster
|
| 57 | 57 | from gajim.gui.groupchat_state import GroupchatState
|
| 58 | 58 | |
| 59 | -log = logging.getLogger('gajim.gui.control')
|
|
| 60 | - |
|
| 59 | +HistoryRowT = Union[events.ApplicationEvent, ConversationRow]
|
|
| 61 | 60 | |
| 62 | 61 | REQUEST_LINES_COUNT = 20
|
| 63 | 62 | |
| 63 | +log = logging.getLogger('gajim.gui.control')
|
|
| 64 | + |
|
| 64 | 65 | |
| 65 | 66 | class ChatControl(EventHelper):
|
| 66 | 67 | def __init__(self) -> None:
|
| 67 | 68 | EventHelper.__init__(self)
|
| 68 | 69 | |
| 69 | 70 | self.handlers: dict[int, Any] = {}
|
| 70 | - |
|
| 71 | 71 | self._contact = None
|
| 72 | 72 | self._client = None
|
| 73 | 73 | |
| ... | ... | @@ -600,13 +600,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None: |
| 600 | 600 | log_line_id)
|
| 601 | 601 | GLib.idle_add(self._scrolled_view.block_signals, False)
|
| 602 | 602 | |
| 603 | - def _request_history(self,
|
|
| 604 | - _scrolled: Gtk.ScrolledWindow,
|
|
| 605 | - before: bool
|
|
| 606 | - ) -> None:
|
|
| 607 | - |
|
| 608 | - self._scrolled_view.block_signals(True)
|
|
| 609 | - |
|
| 603 | + def _request_messages(self, before: bool) -> list[ConversationRow]:
|
|
| 610 | 604 | if before:
|
| 611 | 605 | row = self._scrolled_view.get_first_message_row()
|
| 612 | 606 | else:
|
| ... | ... | @@ -617,62 +611,104 @@ def _request_history(self, |
| 617 | 611 | else:
|
| 618 | 612 | timestamp = row.db_timestamp
|
| 619 | 613 | |
| 614 | + return app.storage.archive.get_conversation_before_after(
|
|
| 615 | + self.contact.account,
|
|
| 616 | + self.contact.jid,
|
|
| 617 | + before,
|
|
| 618 | + timestamp,
|
|
| 619 | + REQUEST_LINES_COUNT)
|
|
| 620 | + |
|
| 621 | + def _request_events(self, before: bool) -> list[events.ApplicationEvent]:
|
|
| 622 | + if before:
|
|
| 623 | + row = self._scrolled_view.get_first_event_row()
|
|
| 624 | + else:
|
|
| 625 | + row = self._scrolled_view.get_last_event_row()
|
|
| 626 | + |
|
| 627 | + if row is None:
|
|
| 628 | + timestamp = time.time()
|
|
| 629 | + else:
|
|
| 630 | + timestamp = row.db_timestamp
|
|
| 631 | + |
|
| 620 | 632 | assert self._contact is not None
|
| 621 | - for event in app.storage.events.load(self._contact, before, timestamp):
|
|
| 622 | - if isinstance(event, events.MUCUserJoined):
|
|
| 623 | - self._process_muc_user_joined(event)
|
|
| 633 | + return app.storage.events.load(self._contact,
|
|
| 634 | + before,
|
|
| 635 | + timestamp,
|
|
| 636 | + REQUEST_LINES_COUNT)
|
|
| 624 | 637 | |
| 625 | - elif isinstance(event, events.MUCUserLeft):
|
|
| 626 | - self._process_muc_user_left(event)
|
|
| 638 | + def _request_history(self,
|
|
| 639 | + _scrolled: Gtk.ScrolledWindow,
|
|
| 640 | + before: bool
|
|
| 641 | + ) -> None:
|
|
| 627 | 642 | |
| 628 | - elif isinstance(event, events.MUCNicknameChanged):
|
|
| 629 | - self._process_muc_nickname_changed(event)
|
|
| 643 | + self._scrolled_view.block_signals(True)
|
|
| 630 | 644 | |
| 631 | - elif isinstance(event, events.MUCRoomKicked):
|
|
| 632 | - self._process_muc_room_kicked(event)
|
|
| 645 | + rows: list[HistoryRowT] = []
|
|
| 633 | 646 | |
| 634 | - elif isinstance(event, events.MUCUserAffiliationChanged):
|
|
| 635 | - self._process_muc_user_affiliation_changed(event)
|
|
| 647 | + messages = self._request_messages(before)
|
|
| 648 | + event_rows = self._request_events(before)
|
|
| 649 | + rows = self._sort_request_rows(messages, event_rows, before)
|
|
| 636 | 650 | |
| 637 | - elif isinstance(event, events.MUCUserRoleChanged):
|
|
| 638 | - self._process_muc_user_role_changed(event)
|
|
| 651 | + assert self._contact is not None
|
|
| 652 | + for row in rows:
|
|
| 653 | + if not isinstance(row, events.ApplicationEvent):
|
|
| 654 | + self.add_messages([row])
|
|
| 639 | 655 | |
| 640 | - elif isinstance(event, events.MUCUserStatusShowChanged):
|
|
| 641 | - self._process_muc_user_status_show_changed(event)
|
|
| 656 | + elif isinstance(row, events.MUCUserJoined):
|
|
| 657 | + self._process_muc_user_joined(row)
|
|
| 642 | 658 | |
| 643 | - elif isinstance(event, events.MUCRoomConfigChanged):
|
|
| 644 | - self._process_muc_room_config_changed(event)
|
|
| 659 | + elif isinstance(row, events.MUCUserLeft):
|
|
| 660 | + self._process_muc_user_left(row)
|
|
| 645 | 661 | |
| 646 | - elif isinstance(event, events.MUCRoomConfigFinished):
|
|
| 647 | - self._process_muc_room_config_finished(event)
|
|
| 662 | + elif isinstance(row, events.MUCNicknameChanged):
|
|
| 663 | + self._process_muc_nickname_changed(row)
|
|
| 648 | 664 | |
| 649 | - elif isinstance(event, events.MUCRoomPresenceError):
|
|
| 650 | - self._process_muc_room_presence_error(event)
|
|
| 665 | + elif isinstance(row, events.MUCRoomKicked):
|
|
| 666 | + self._process_muc_room_kicked(row)
|
|
| 651 | 667 | |
| 652 | - elif isinstance(event, events.MUCRoomDestroyed):
|
|
| 653 | - self._process_muc_room_destroyed(event)
|
|
| 654 | - else:
|
|
| 655 | - raise ValueError('Unknown event: %s' % event)
|
|
| 668 | + elif isinstance(row, events.MUCUserAffiliationChanged):
|
|
| 669 | + self._process_muc_user_affiliation_changed(row)
|
|
| 656 | 670 | |
| 657 | - messages = app.storage.archive.get_conversation_before_after(
|
|
| 658 | - self.contact.account,
|
|
| 659 | - self.contact.jid,
|
|
| 660 | - before,
|
|
| 661 | - timestamp,
|
|
| 662 | - REQUEST_LINES_COUNT)
|
|
| 671 | + elif isinstance(row, events.MUCUserRoleChanged):
|
|
| 672 | + self._process_muc_user_role_changed(row)
|
|
| 663 | 673 | |
| 664 | - if not messages:
|
|
| 665 | - self._scrolled_view.set_history_complete(before, True)
|
|
| 666 | - self._scrolled_view.block_signals(False)
|
|
| 667 | - return
|
|
| 674 | + elif isinstance(row, events.MUCUserStatusShowChanged):
|
|
| 675 | + self._process_muc_user_status_show_changed(row)
|
|
| 676 | + |
|
| 677 | + elif isinstance(row, events.MUCRoomConfigChanged):
|
|
| 678 | + self._process_muc_room_config_changed(row)
|
|
| 679 | + |
|
| 680 | + elif isinstance(row, events.MUCRoomConfigFinished):
|
|
| 681 | + self._process_muc_room_config_finished(row)
|
|
| 668 | 682 | |
| 669 | - self.add_messages(messages)
|
|
| 683 | + elif isinstance(row, events.MUCRoomPresenceError):
|
|
| 684 | + self._process_muc_room_presence_error(row)
|
|
| 670 | 685 | |
| 671 | - if len(messages) < REQUEST_LINES_COUNT:
|
|
| 686 | + elif isinstance(row, events.MUCRoomDestroyed):
|
|
| 687 | + self._process_muc_room_destroyed(row)
|
|
| 688 | + |
|
| 689 | + else:
|
|
| 690 | + raise ValueError('Unknown event: %s' % type(row))
|
|
| 691 | + |
|
| 692 | + if len(rows) < REQUEST_LINES_COUNT:
|
|
| 672 | 693 | self._scrolled_view.set_history_complete(before, True)
|
| 673 | 694 | |
| 674 | 695 | self._scrolled_view.block_signals(False)
|
| 675 | 696 | |
| 697 | + @staticmethod
|
|
| 698 | + def _sort_request_rows(messages: list[ConversationRow],
|
|
| 699 | + event_rows: list[events.ApplicationEvent],
|
|
| 700 | + before: bool
|
|
| 701 | + ) -> list[HistoryRowT]:
|
|
| 702 | + |
|
| 703 | + def sort_func(obj: HistoryRowT) -> float:
|
|
| 704 | + if isinstance(obj, events.ApplicationEvent):
|
|
| 705 | + return obj.timestamp # pyright: ignore
|
|
| 706 | + return obj.time
|
|
| 707 | + |
|
| 708 | + rows = messages + event_rows
|
|
| 709 | + rows.sort(key=sort_func, reverse=before)
|
|
| 710 | + return rows
|
|
| 711 | + |
|
| 676 | 712 | def add_messages(self, messages: list[ConversationRow]):
|
| 677 | 713 | for msg in messages:
|
| 678 | 714 | if msg.kind in (KindConstant.FILE_TRANSFER_INCOMING,
|
| ... | ... | @@ -390,6 +390,20 @@ def get_last_message_row(self) -> Optional[MessageRow]: |
| 390 | 390 | return row
|
| 391 | 391 | return None
|
| 392 | 392 | |
| 393 | + def get_first_event_row(self) -> Optional[Union[InfoMessage, MUCJoinLeft]]:
|
|
| 394 | + for row in self._list_box.get_children():
|
|
| 395 | + if isinstance(row, (InfoMessage, MUCJoinLeft)):
|
|
| 396 | + return row
|
|
| 397 | + return None
|
|
| 398 | + |
|
| 399 | + def get_last_event_row(self) -> Optional[Union[InfoMessage, MUCJoinLeft]]:
|
|
| 400 | + children = self._list_box.get_children()
|
|
| 401 | + children.reverse()
|
|
| 402 | + for row in children:
|
|
| 403 | + if isinstance(row, (InfoMessage, MUCJoinLeft)):
|
|
| 404 | + return row
|
|
| 405 | + return None
|
|
| 406 | + |
|
| 393 | 407 | @staticmethod
|
| 394 | 408 | def _sort_func(row1: BaseRow, row2: BaseRow) -> int:
|
| 395 | 409 | if row1.timestamp == row2.timestamp:
|