Rooms and threads fixes

- Fix roomList height again, now based on model.count().
  All delegates are assumed to be the same height

- Properly update room list when a room is joined or left

- Catch exceptions happening in threads (futures), which previously
  passed silently

- Show "Empty room?" as "<i>Empty Room</i>" + gray [?] avatar
This commit is contained in:
miruka 2019-04-13 08:59:10 -04:00
parent d8c6ffefe0
commit 13fca98838
8 changed files with 58 additions and 36 deletions

View File

@ -2,8 +2,11 @@
# This file is part of harmonyqml, licensed under GPLv3. # This file is part of harmonyqml, licensed under GPLv3.
import functools import functools
import logging
import sys
import traceback
from concurrent.futures import Future, ThreadPoolExecutor from concurrent.futures import Future, ThreadPoolExecutor
from threading import Event from threading import Event, currentThread
from typing import Callable, DefaultDict, Dict from typing import Callable, DefaultDict, Dict
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
@ -22,7 +25,16 @@ _POOLS: DefaultDict[str, ThreadPoolExecutor] = \
def futurize(func: Callable) -> Callable: def futurize(func: Callable) -> Callable:
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs) -> Future: def wrapper(*args, **kwargs) -> Future:
return args[0].pool.submit(func, *args, **kwargs) # args[0] = self def run_and_catch_errs():
# Without this, exceptions are silently ignored
try:
func(*args, **kwargs)
except Exception:
traceback.print_exc()
logging.error("Exiting %s due to exception.", currentThread())
sys.exit(1)
return args[0].pool.submit(run_and_catch_errs) # args[0] = self
return wrapper return wrapper
@ -91,7 +103,7 @@ class Client(QObject):
@futurize @futurize
def startSyncing(self) -> None: def startSyncing(self) -> None:
while True: while True:
self._on_sync(self.net.talk(self.nio.sync, timeout=10)) self._on_sync(self.net.talk(self.nio.sync, timeout=10_000))
if self._stop_sync.is_set(): if self._stop_sync.is_set():
self._stop_sync.clear() self._stop_sync.clear()
@ -105,7 +117,7 @@ class Client(QObject):
for room_id in response.rooms.join: for room_id in response.rooms.join:
self.roomJoined.emit(room_id) self.roomJoined.emit(room_id)
for room_id in response.rooms.left: for room_id in response.rooms.leave:
self.roomLeft.emit(room_id) self.roomLeft.emit(room_id)

View File

@ -17,7 +17,7 @@ class User(NamedTuple):
class Room(NamedTuple): class Room(NamedTuple):
room_id: str room_id: str
display_name: str display_name: Optional[str]
description: str = "" description: str = ""
unread_messages: int = 0 unread_messages: int = 0
presence: Presence = Presence.none presence: Presence = Presence.none

View File

@ -129,7 +129,7 @@ class ListModel(QAbstractListModel):
@pyqtSlot(int, list) @pyqtSlot(int, list)
def set(self, index: int, value: NewValue) -> None: def set(self, index: int, value: NewValue) -> None:
qidx = QAbstractListModel.index(index, 0) qidx = QAbstractListModel.index(self, index, 0)
value = self._convert_new_value(value) value = self._convert_new_value(value)
self._list[index] = value self._list[index] = value
self.dataChanged.emit(qidx, qidx, self.roleNames()) self.dataChanged.emit(qidx, qidx, self.roleNames())
@ -139,7 +139,7 @@ class ListModel(QAbstractListModel):
@pyqtSlot(int, str, "QVariant") @pyqtSlot(int, str, "QVariant")
def setProperty(self, index: int, prop: str, value: Any) -> None: def setProperty(self, index: int, prop: str, value: Any) -> None:
self._list[index][self._roles.index(prop)] = value self._list[index][self._roles.index(prop)] = value
qidx = QAbstractListModel.index(index, 0) qidx = QAbstractListModel.index(self, index, 0)
self.dataChanged.emit(qidx, qidx, self.roleNames()) self.dataChanged.emit(qidx, qidx, self.roleNames())
self._update_count += 1 self._update_count += 1

View File

@ -85,14 +85,12 @@ class NetworkManager:
except NioErrorResponse as err: except NioErrorResponse as err:
logging.error("read bad response for %s: %s", nio_func, err) logging.error("read bad response for %s: %s", nio_func, err)
self._close_socket(sock) self._close_socket(sock)
if self._should_abort_talk(err): if self._should_abort_talk(err):
logging.error("aborting talk") logging.error("aborting talk")
break break
time.sleep(10)
except Exception as err: time.sleep(10)
logging.error("talk exception for %s: %r", nio_func, err)
break
else: else:
break break

View File

@ -1,11 +1,13 @@
# Copyright 2019 miruka # Copyright 2019 miruka
# This file is part of harmonyqml, licensed under GPLv3. # This file is part of harmonyqml, licensed under GPLv3.
from typing import Optional
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
from .backend import Backend from .backend import Backend
from .client import Client from .client import Client
from .model.items import User, Room from .model.items import Room, User
class SignalManager(QObject): class SignalManager(QObject):
@ -43,14 +45,25 @@ class SignalManager(QObject):
def onRoomJoined(self, client: Client, room_id: str) -> None: def onRoomJoined(self, client: Client, room_id: str) -> None:
model = self.backend.models.rooms[client.userID]
room = client.nio.rooms[room_id] room = client.nio.rooms[room_id]
name = room.name or room.canonical_alias or room.group_name()
self.backend.models.rooms[client.userID].append(Room( def group_name() -> Optional[str]:
name = room.group_name()
return None if name == "Empty room?" else name
item = Room(
room_id = room_id, room_id = room_id,
display_name = name, display_name = room.name or room.canonical_alias or group_name(),
description = getattr(room, "topic", ""), # FIXME: outside init description = getattr(room, "topic", ""), # FIXME: outside init
)) )
try:
index = model.indexWhere("room_id", room_id)
except ValueError:
model.append(item)
else:
model[index] = item
def onRoomLeft(self, client: Client, room_id: str) -> None: def onRoomLeft(self, client: Client, room_id: str) -> None:

View File

@ -4,7 +4,7 @@ import QtQuick.Layouts 1.4
Item { Item {
property bool invisible: false property bool invisible: false
property string name: "?" property var name: null
property var imageSource: null property var imageSource: null
property int dimmension: 48 property int dimmension: 48
@ -16,11 +16,13 @@ Item {
id: "letterRectangle" id: "letterRectangle"
anchors.fill: parent anchors.fill: parent
visible: ! invisible && imageSource === null visible: ! invisible && imageSource === null
color: Qt.hsla(Backend.hueFromString(name), 0.22, 0.5, 1) color: name ?
Qt.hsla(Backend.hueFromString(name), 0.22, 0.5, 1) :
Qt.hsla(0, 0, 0.22, 1)
HLabel { HLabel {
anchors.centerIn: parent anchors.centerIn: parent
text: name.charAt(0) text: name ? name.charAt(0) : "?"
color: "white" color: "white"
font.pixelSize: letterRectangle.height / 1.4 font.pixelSize: letterRectangle.height / 1.4
} }

View File

@ -6,7 +6,7 @@ import "../base" as Base
MouseArea { MouseArea {
id: "root" id: "root"
width: roomList.width width: roomList.width
height: Math.max(roomLabel.height + subtitleLabel.height, avatar.height) height: roomList.childrenHeight
onClicked: pageStack.show_room( onClicked: pageStack.show_room(
roomList.user_id, roomList.user_id,
@ -18,14 +18,15 @@ MouseArea {
id: row id: row
spacing: 1 spacing: 1
Base.Avatar { id: avatar; name: display_name; dimmension: 36 } Base.Avatar { id: avatar; name: display_name; dimmension: root.height }
ColumnLayout { ColumnLayout {
spacing: 0 spacing: 0
Base.HLabel { Base.HLabel {
id: roomLabel id: roomLabel
text: display_name text: display_name ? display_name : "<i>Empty room</i>"
textFormat: Text.StyledText
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
Layout.maximumWidth: row.width - row.spacing - avatar.width Layout.maximumWidth: row.width - row.spacing - avatar.width
@ -37,11 +38,12 @@ MouseArea {
rightPadding: leftPadding rightPadding: leftPadding
} }
Base.HLabel { Base.HLabel {
function get_text() { property var msgModel: Backend.models.messages.get(room_id)
var msgs = Backend.models.messages.get(room_id)
if (! msgs || msgs.count < 1) { return "" }
var msg = msgs.get(-1) function get_text() {
if (msgModel.count < 1) { return "" }
var msg = msgModel.get(-1)
var color_ = (msg.sender_id === roomList.user_id ? var color_ = (msg.sender_id === roomList.user_id ?
"darkblue" : "purple") "darkblue" : "purple")
var client = Backend.clientManager.clients[RoomList.for_user_id] var client = Backend.clientManager.clients[RoomList.for_user_id]
@ -54,7 +56,7 @@ MouseArea {
id: subtitleLabel id: subtitleLabel
visible: text !== "" visible: text !== ""
text: Backend.models.messages.get(room_id).reloadThis, get_text() text: msgModel.reloadThis, get_text()
textFormat: Text.StyledText textFormat: Text.StyledText
font.pixelSize: smallSize font.pixelSize: smallSize

View File

@ -6,17 +6,12 @@ import "../base" as Base
ListView { ListView {
property var for_user_id: null property var for_user_id: null
property int childrenHeight: 36
property int contentHeight: 0 property int contentHeight: 0
onCountChanged: { onCountChanged: {
var children = roomList.contentItem.children contentHeight = childrenHeight * model.count +
var childrenHeight = 0 spacing * (model.count - 1)
for (var i = 0; i < children.length; i++) {
childrenHeight += children[i].height
}
contentHeight = childrenHeight + spacing * (children.length - 1)
} }
id: "roomList" id: "roomList"