From 83c127183b9ef86cac06fb7cd7b0674b4615e30b Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 08:36:48 +0200 Subject: [PATCH 01/75] [Store] New databasescheme --- lib/src/Store.dart | 100 ++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 80bb54e3..cedcf87a 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -699,73 +699,69 @@ class Store { 'UNIQUE(client))', /// The database scheme for the Room class. - "Rooms": 'CREATE TABLE IF NOT EXISTS Rooms(' + - 'id TEXT PRIMARY KEY, ' + + 'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' + + 'room_id TEXT PRIMARY KEY, ' + 'membership TEXT, ' + - 'topic TEXT, ' + 'highlight_count INTEGER, ' + 'notification_count INTEGER, ' + + 'prev_batch TEXT, ' + 'joined_member_count INTEGER, ' + 'invited_member_count INTEGER, ' + - 'heroes TEXT, ' + - 'prev_batch TEXT, ' + - 'avatar_url TEXT, ' + - 'draft TEXT, ' + - 'unread INTEGER, ' + // Timestamp of when the user has last read the chat - 'fully_read TEXT, ' + // ID of the fully read marker event - 'description TEXT, ' + - 'canonical_alias TEXT, ' + // The address in the form: #roomname:homeserver.org - 'direct_chat_matrix_id TEXT, ' + //If this room is a direct chat, this is the matrix ID of the user - 'notification_settings TEXT, ' + // Must be one of [all, mention] - - // Security rules - 'guest_access TEXT, ' + - 'history_visibility TEXT, ' + - 'join_rules TEXT, ' + - - // Power levels - 'power_events_default INTEGER, ' + - 'power_state_default INTEGER, ' + - 'power_redact INTEGER, ' + - 'power_invite INTEGER, ' + - 'power_ban INTEGER, ' + - 'power_kick INTEGER, ' + - 'power_user_default INTEGER, ' + - - // Power levels for events - 'power_event_avatar INTEGER, ' + - 'power_event_history_visibility INTEGER, ' + - 'power_event_canonical_alias INTEGER, ' + - 'power_event_aliases INTEGER, ' + - 'power_event_name INTEGER, ' + - 'power_event_power_levels INTEGER, ' + 'UNIQUE(id))', - /// The database scheme for the Event class. - "Events": 'CREATE TABLE IF NOT EXISTS Events(' + - 'id TEXT PRIMARY KEY, ' + - 'chat_id TEXT, ' + + /// The users which can be used to generate a room name if the room does not have one. + 'Heroes': 'CREATE TABLE IF NOT EXISTS Heroes(' + + 'room_id TEXT PRIMARY KEY, ' + + 'matrix_id TEXT, ' + + 'UNIQUE(room_id,matrix_id))', + + /// The database scheme for the TimelineEvent class. + 'Events': 'CREATE TABLE IF NOT EXISTS Events(' + + 'event_id TEXT PRIMARY KEY, ' + + 'room_id TEXT, ' + 'origin_server_ts INTEGER, ' + 'sender TEXT, ' + - 'state_key TEXT, ' + - 'content_body TEXT, ' + 'type TEXT, ' + - 'content_json TEXT, ' + + 'unsigned TEXT, ' + + 'content TEXT, ' + "status INTEGER, " + 'UNIQUE(id))', - /// The database scheme for the User class. - "Users": 'CREATE TABLE IF NOT EXISTS Users(' + - 'chat_id TEXT, ' + // The chat id of this membership - 'matrix_id TEXT, ' + // The matrix id of this user - 'displayname TEXT, ' + - 'avatar_url TEXT, ' + - 'membership TEXT, ' + // The status of the membership. Must be one of [join, invite, ban, leave] - 'power_level INTEGER, ' + // The power level of this user. Must be in [0,..,100] - 'UNIQUE(chat_id, matrix_id))', + /// The database scheme for room states. + 'State': 'CREATE TABLE IF NOT EXISTS State(' + + 'event_id TEXT PRIMARY KEY, ' + + 'room_id TEXT, ' + + 'origin_server_ts INTEGER, ' + + 'sender TEXT, ' + + 'state_key TEXT, ' + + 'unsigned TEXT, ' + + 'prev_content TEXT, ' + + 'type TEXT, ' + + 'content TEXT, ' + + 'UNIQUE(room_id,state_key,type))', + + /// The database scheme for room states. + 'AccountData': 'CREATE TABLE IF NOT EXISTS AccountData(' + + 'type TEXT PRIMARY KEY, ' + + 'content TEXT, ' + + 'UNIQUE(type))', + + /// The database scheme for room states. + 'RoomAccountData': 'CREATE TABLE IF NOT EXISTS RoomAccountData(' + + 'type TEXT PRIMARY KEY, ' + + 'room_id TEXT, ' + + 'content TEXT, ' + + 'UNIQUE(type,room_id))', + + /// The database scheme for room states. + 'Presence': 'CREATE TABLE IF NOT EXISTS Presence(' + + 'type TEXT PRIMARY KEY, ' + + 'sender TEXT, ' + + 'content TEXT, ' + + 'UNIQUE(sender))', /// The database scheme for the NotificationsCache class. - "NotificationsCache": 'CREATE TABLE IF NOT EXISTS NotificationsCache(' + + 'NotificationsCache': 'CREATE TABLE IF NOT EXISTS NotificationsCache(' + 'id int PRIMARY KEY, ' + 'chat_id TEXT, ' + // The chat id 'event_id TEXT, ' + // The matrix id of the Event From 92d8ab92066d3e01ed693a57990f353bf4c3bff1 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 08:43:28 +0200 Subject: [PATCH 02/75] [Store] Only store state events --- lib/src/Store.dart | 223 +++++++-------------------------------------- 1 file changed, 32 insertions(+), 191 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index cedcf87a..7179270e 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -229,45 +229,36 @@ class Store { String type = eventUpdate.type; String chat_id = eventUpdate.roomID; + // Get the state_key for m.room.member events + String state_key = ""; + if (eventContent["state_key"] is String) { + state_key = eventContent["state_key"]; + } + if (type == "timeline" || type == "history") { // calculate the status num status = 2; if (eventContent["status"] is num) status = eventContent["status"]; - // Make unsigned part of the content - if (eventContent.containsKey("unsigned")) { - Map newContent = { - "unsigned": eventContent["unsigned"] - }; - eventContent["content"].forEach((key, val) => newContent[key] = val); - eventContent["content"] = newContent; - } - - // Get the state_key for m.room.member events - String state_key = ""; - if (eventContent["state_key"] is String) { - state_key = eventContent["state_key"]; - } - // Save the event in the database if ((status == 1 || status == -1) && eventContent["unsigned"] is Map && eventContent["unsigned"]["transaction_id"] is String) - txn.rawUpdate("UPDATE Events SET status=?, id=? WHERE id=?", [ + txn.rawUpdate( + "UPDATE Events SET status=?, event_id=? WHERE event_id=?", [ status, eventContent["event_id"], eventContent["unsigned"]["transaction_id"] ]); else txn.rawInsert( - "INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + "INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?)", [ eventContent["event_id"], chat_id, eventContent["origin_server_ts"], eventContent["sender"], - state_key, - eventContent["content"]["body"], eventContent["type"], + json.encode(eventContent["unsigned"] ?? ""), json.encode(eventContent["content"]), status ]); @@ -276,184 +267,34 @@ class Store { if (status != -1 && eventUpdate.content.containsKey("unsigned") && eventUpdate.content["unsigned"]["transaction_id"] is String) - txn.rawDelete("DELETE FROM Events WHERE id=?", + txn.rawDelete("DELETE FROM Events WHERE event_id=?", [eventUpdate.content["unsigned"]["transaction_id"]]); } if (type == "history") return null; - switch (eventUpdate.eventType) { - case "m.receipt": - if (eventContent["user"] == client.userID) { - txn.rawUpdate("UPDATE Rooms SET unread=? WHERE id=?", - [eventContent["ts"], chat_id]); - } else { - // Mark all previous received messages as seen - txn.rawUpdate( - "UPDATE Events SET status=3 WHERE origin_server_ts<=? AND chat_id=? AND status=2", - [eventContent["ts"], chat_id]); - } - break; - // This event means, that the name of a room has been changed, so - // it has to be changed in the database. - case "m.room.name": - txn.rawUpdate("UPDATE Rooms SET topic=? WHERE id=?", - [eventContent["content"]["name"], chat_id]); - break; - // This event means, that the topic of a room has been changed, so - // it has to be changed in the database - case "m.room.topic": - txn.rawUpdate("UPDATE Rooms SET description=? WHERE id=?", - [eventContent["content"]["topic"], chat_id]); - break; - // This event means, that the topic of a room has been changed, so - // it has to be changed in the database - case "m.room.history_visibility": - txn.rawUpdate("UPDATE Rooms SET history_visibility=? WHERE id=?", - [eventContent["content"]["history_visibility"], chat_id]); - break; - // This event means, that the topic of a room has been changed, so - // it has to be changed in the database - case "m.room.redaction": - txn.rawDelete( - "DELETE FROM Events WHERE id=?", [eventContent["redacts"]]); - break; - // This event means, that the topic of a room has been changed, so - // it has to be changed in the database - case "m.room.guest_access": - txn.rawUpdate("UPDATE Rooms SET guest_access=? WHERE id=?", - [eventContent["content"]["guest_access"], chat_id]); - break; - // This event means, that the canonical alias of a room has been changed, so - // it has to be changed in the database - case "m.room.canonical_alias": - txn.rawUpdate("UPDATE Rooms SET canonical_alias=? WHERE id=?", - [eventContent["content"]["alias"], chat_id]); - break; - // This event means, that the topic of a room has been changed, so - // it has to be changed in the database - case "m.room.join_rules": - txn.rawUpdate("UPDATE Rooms SET join_rules=? WHERE id=?", - [eventContent["content"]["join_rule"], chat_id]); - break; - // This event means, that the avatar of a room has been changed, so - // it has to be changed in the database - case "m.room.avatar": - txn.rawUpdate("UPDATE Rooms SET avatar_url=? WHERE id=?", - [eventContent["content"]["url"], chat_id]); - break; - // This event means, that the aliases of a room has been changed, so - // it has to be changed in the database - case "m.fully_read": - txn.rawUpdate("UPDATE Rooms SET fully_read=? WHERE id=?", - [eventContent["content"]["event_id"], chat_id]); - break; - // This event means, that someone joined the room, has left the room - // or has changed his nickname - case "m.room.member": - String membership = eventContent["content"]["membership"]; - String state_key = eventContent["state_key"]; - String insertDisplayname = ""; - String insertAvatarUrl = ""; - if (eventContent["content"]["displayname"] is String) { - insertDisplayname = eventContent["content"]["displayname"]; - } - if (eventContent["content"]["avatar_url"] is String) { - insertAvatarUrl = eventContent["content"]["avatar_url"]; - } + if (eventUpdate.content["event_id"] != null) { + txn.rawInsert( + "INSERT OR REPLACE INTO State VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + eventContent["event_id"], + chat_id, + eventContent["origin_server_ts"], + eventContent["sender"], + state_key, + json.encode(eventContent["unsigned"] ?? ""), + json.encode(eventContent["prev_content"] ?? ""), + eventContent["type"], + json.encode(eventContent["content"]), + ]); + } else + txn.rawInsert( + "INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", + [ + eventContent["type"], + chat_id, + json.encode(eventContent["content"]), + ]); - // Update membership table - txn.rawInsert("INSERT OR IGNORE INTO Users VALUES(?,?,?,?,?,0)", [ - chat_id, - state_key, - insertDisplayname, - insertAvatarUrl, - membership - ]); - String queryStr = "UPDATE Users SET membership=?"; - List queryArgs = [membership]; - - if (eventContent["content"]["displayname"] is String) { - queryStr += " , displayname=?"; - queryArgs.add(eventContent["content"]["displayname"]); - } - if (eventContent["content"]["avatar_url"] is String) { - queryStr += " , avatar_url=?"; - queryArgs.add(eventContent["content"]["avatar_url"]); - } - - queryStr += " WHERE matrix_id=? AND chat_id=?"; - queryArgs.add(state_key); - queryArgs.add(chat_id); - txn.rawUpdate(queryStr, queryArgs); - break; - // This event changes the permissions of the users and the power levels - case "m.room.power_levels": - String query = "UPDATE Rooms SET "; - if (eventContent["content"]["ban"] is num) - query += ", power_ban=" + eventContent["content"]["ban"].toString(); - if (eventContent["content"]["events_default"] is num) - query += ", power_events_default=" + - eventContent["content"]["events_default"].toString(); - if (eventContent["content"]["state_default"] is num) - query += ", power_state_default=" + - eventContent["content"]["state_default"].toString(); - if (eventContent["content"]["redact"] is num) - query += - ", power_redact=" + eventContent["content"]["redact"].toString(); - if (eventContent["content"]["invite"] is num) - query += - ", power_invite=" + eventContent["content"]["invite"].toString(); - if (eventContent["content"]["kick"] is num) - query += ", power_kick=" + eventContent["content"]["kick"].toString(); - if (eventContent["content"]["user_default"] is num) - query += ", power_user_default=" + - eventContent["content"]["user_default"].toString(); - if (eventContent["content"]["events"] is Map) { - if (eventContent["content"]["events"]["m.room.avatar"] is num) - query += ", power_event_avatar=" + - eventContent["content"]["events"]["m.room.avatar"].toString(); - if (eventContent["content"]["events"]["m.room.history_visibility"] - is num) - query += ", power_event_history_visibility=" + - eventContent["content"]["events"]["m.room.history_visibility"] - .toString(); - if (eventContent["content"]["events"]["m.room.canonical_alias"] - is num) - query += ", power_event_canonical_alias=" + - eventContent["content"]["events"]["m.room.canonical_alias"] - .toString(); - if (eventContent["content"]["events"]["m.room.aliases"] is num) - query += ", power_event_aliases=" + - eventContent["content"]["events"]["m.room.aliases"].toString(); - if (eventContent["content"]["events"]["m.room.name"] is num) - query += ", power_event_name=" + - eventContent["content"]["events"]["m.room.name"].toString(); - if (eventContent["content"]["events"]["m.room.power_levels"] is num) - query += ", power_event_power_levels=" + - eventContent["content"]["events"]["m.room.power_levels"] - .toString(); - } - if (query != "UPDATE Rooms SET ") { - query = query.replaceFirst(",", ""); - txn.rawUpdate(query + " WHERE id=?", [chat_id]); - } - - // Set the users power levels: - if (eventContent["content"]["users"] is Map) { - eventContent["content"]["users"] - .forEach((String user, dynamic value) async { - num power_level = eventContent["content"]["users"][user]; - txn.rawUpdate( - "UPDATE Users SET power_level=? WHERE matrix_id=? AND chat_id=?", - [power_level, user, chat_id]); - txn.rawInsert( - "INSERT OR IGNORE INTO Users VALUES(?, ?, '', '', ?, ?)", - [chat_id, user, "unknown", power_level]); - }); - } - break; - } return null; } From 006c5a756fe83d2a9431bad4da35989f3f156d49 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 08:44:49 +0200 Subject: [PATCH 03/75] [Store] Fix RoomUpdate storing --- lib/src/Store.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 7179270e..2f23b2fc 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -163,8 +163,7 @@ class Store { Future storeRoomUpdate(RoomUpdate roomUpdate) { // Insert the chat into the database if not exists txn.rawInsert( - "INSERT OR IGNORE INTO Rooms " + - "VALUES(?, ?, '', 0, 0, 0, 0, '', '', '', '', 0, '', '', '', '', '', '', '', '', 0, 50, 50, 0, 50, 50, 0, 50, 100, 50, 50, 50, 100) ", + "INSERT OR IGNORE INTO Rooms " + "VALUES(?, ?, 0, 0, '', 0, 0) ", [roomUpdate.id, roomUpdate.membership.toString().split('.').last]); // Update the notification counts and the limited timeline boolean and the summary From 8b8232d2bac2e7545d5a02a1fabe3db52d4a27b4 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 08:47:53 +0200 Subject: [PATCH 04/75] [Store] Correct storing of accoundata --- lib/src/Store.dart | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 2f23b2fc..367ef9ce 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -203,21 +203,10 @@ class Store { /// Stores an UserUpdate object in the database. Must be called inside of /// [transaction]. Future storeUserEventUpdate(UserUpdate userUpdate) { - switch (userUpdate.eventType) { - case "m.direct": - if (userUpdate.content["content"] is Map) { - final Map directMap = userUpdate.content["content"]; - directMap.forEach((String key, dynamic value) { - if (value is List && value.length > 0) - for (int i = 0; i < value.length; i++) { - txn.rawUpdate( - "UPDATE Rooms SET direct_chat_matrix_id=? WHERE id=?", - [key, value[i]]); - } - }); - } - break; - } + txn.rawInsert("INSERT OR REPLACE INTO AccountData VALUES(?, ?)", [ + userUpdate.eventType, + json.encode(userUpdate.content["content"]), + ]); return null; } @@ -286,13 +275,11 @@ class Store { json.encode(eventContent["content"]), ]); } else - txn.rawInsert( - "INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", - [ - eventContent["type"], - chat_id, - json.encode(eventContent["content"]), - ]); + txn.rawInsert("INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?)", [ + eventContent["type"], + chat_id, + json.encode(eventContent["content"]), + ]); return null; } From 83d747f5f5299236c0d3f6d3a964796849bf163e Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 08:50:05 +0200 Subject: [PATCH 05/75] [Store] Handle presences in store --- lib/src/Store.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 367ef9ce..236b79d9 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -203,10 +203,17 @@ class Store { /// Stores an UserUpdate object in the database. Must be called inside of /// [transaction]. Future storeUserEventUpdate(UserUpdate userUpdate) { - txn.rawInsert("INSERT OR REPLACE INTO AccountData VALUES(?, ?)", [ - userUpdate.eventType, - json.encode(userUpdate.content["content"]), - ]); + if (userUpdate.type == "account_data") + txn.rawInsert("INSERT OR REPLACE INTO AccountData VALUES(?, ?)", [ + userUpdate.eventType, + json.encode(userUpdate.content["content"]), + ]); + else if (userUpdate.type == "presence") + txn.rawInsert("INSERT OR REPLACE INTO Presence VALUES(?, ?)", [ + userUpdate.eventType, + userUpdate.content["sender"], + json.encode(userUpdate.content["content"]), + ]); return null; } From 930404d14986d9f52c9e5c58a6097a9aeb8c5478 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 09:05:03 +0200 Subject: [PATCH 06/75] [Store] Rewrite queries --- lib/src/Store.dart | 158 +++++++-------------------------------------- 1 file changed, 23 insertions(+), 135 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 236b79d9..116a3f26 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -163,7 +163,7 @@ class Store { Future storeRoomUpdate(RoomUpdate roomUpdate) { // Insert the chat into the database if not exists txn.rawInsert( - "INSERT OR IGNORE INTO Rooms " + "VALUES(?, ?, 0, 0, '', 0, 0) ", + "INSERT OR IGNORE INTO Rooms " + "VALUES(?, ?, 0, 0, '', 0, 0, '') ", [roomUpdate.id, roomUpdate.membership.toString().split('.').last]); // Update the notification counts and the limited timeline boolean and the summary @@ -294,7 +294,7 @@ class Store { /// Returns a User object by a given Matrix ID and a Room. Future getUser({String matrixID, Room room}) async { List> res = await db.rawQuery( - "SELECT * FROM Users WHERE matrix_id=? AND chat_id=?", + "SELECT * FROM States WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; return User.fromJson(res[0], room); @@ -304,7 +304,7 @@ class Store { /// except users who are in the Room with the ID [exceptRoomID]. Future> loadContacts({String exceptRoomID = ""}) async { List> res = await db.rawQuery( - "SELECT * FROM Users WHERE matrix_id!=? AND chat_id!=? GROUP BY matrix_id ORDER BY displayname", + "SELECT * FROM States WHERE state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key", [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) @@ -316,9 +316,9 @@ class Store { Future> loadParticipants(Room room) async { List> res = await db.rawQuery( "SELECT * " + - " FROM Users " + - " WHERE chat_id=? " + - " AND membership='join'", + " FROM States " + + " WHERE room_id=? " + + " AND type='m.room.member'", [room.id]); List participants = []; @@ -332,26 +332,18 @@ class Store { /// Returns a list of events for the given room and sets all participants. Future> getEventList(Room room) async { - List> memberRes = await db.rawQuery( - "SELECT * " + " FROM Users " + " WHERE Users.chat_id=?", [room.id]); - Map userMap = {}; - for (num i = 0; i < memberRes.length; i++) - userMap[memberRes[i]["matrix_id"]] = User.fromJson(memberRes[i], room); - List> eventRes = await db.rawQuery( "SELECT * " + - " FROM Events events " + - " WHERE events.chat_id=?" + - " GROUP BY events.id " + + " FROM Events " + + " WHERE room_id=?" + + " GROUP BY id " + " ORDER BY origin_server_ts DESC", [room.id]); List eventList = []; for (num i = 0; i < eventRes.length; i++) - eventList.add(Event.fromJson(eventRes[i], room, - senderUser: userMap[eventRes[i]["sender"]], - stateKeyUser: userMap[eventRes[i]["state_key"]])); + eventList.add(Event.fromJson(eventRes[i], room)); return eventList; } @@ -362,25 +354,18 @@ class Store { bool onlyDirect = false, bool onlyGroups = false}) async { if (onlyDirect && onlyGroups) return []; - List> res = await db.rawQuery( - "SELECT rooms.*, events.origin_server_ts, events.content_json, events.type, events.sender, events.status, events.state_key " + - " FROM Rooms rooms LEFT JOIN Events events " + - " ON rooms.id=events.chat_id " + - " WHERE rooms.membership" + - (onlyLeft ? "=" : "!=") + - "'leave' " + - (onlyDirect ? " AND rooms.direct_chat_matrix_id!= '' " : "") + - (onlyGroups ? " AND rooms.direct_chat_matrix_id= '' " : "") + - " GROUP BY rooms.id " + - " ORDER BY origin_server_ts DESC "); + List> res = await db.rawQuery("SELECT * " + + " FROM Rooms" + + " WHERE rooms.membership" + + (onlyLeft ? "=" : "!=") + + "'leave' " + + " GROUP BY rooms.id " + + " ORDER BY origin_server_ts DESC "); List roomList = []; for (num i = 0; i < res.length; i++) { - try { - Room room = await Room.getRoomFromTableRow(res[i], client); - roomList.add(room); - } catch (e) { - print(e.toString()); - } + Room room = await Room.getRoomFromTableRow( + res[i], client); // TODO: Also query the states for a room + roomList.add(room); } return roomList; } @@ -393,97 +378,6 @@ class Store { return Room.getRoomFromTableRow(res[0], client); } - /// Returns a room without events and participants. - Future getRoomByAlias(String alias) async { - List> res = await db - .rawQuery("SELECT * FROM Rooms WHERE canonical_alias=?", [alias]); - if (res.length != 1) return null; - return Room.getRoomFromTableRow(res[0], client); - } - - /// Calculates and returns an avatar for a direct chat by a given [roomID]. - Future getAvatarFromSingleChat(String roomID) async { - String avatarStr = ""; - List> res = await db.rawQuery( - "SELECT avatar_url FROM Users " + - " WHERE Users.chat_id=? " + - " AND (Users.membership='join' OR Users.membership='invite') " + - " AND Users.matrix_id!=? ", - [roomID, client.userID]); - if (res.length == 1) avatarStr = res[0]["avatar_url"]; - return avatarStr; - } - - /// Calculates a chat name for a groupchat without a name. The chat name will - /// be the name of all users (excluding the user of this client) divided by - /// ','. - Future getChatNameFromMemberNames(String roomID) async { - String displayname = 'Empty chat'; - List> rs = await db.rawQuery( - "SELECT Users.displayname, Users.matrix_id, Users.membership FROM Users " + - " WHERE Users.chat_id=? " + - " AND (Users.membership='join' OR Users.membership='invite') " + - " AND Users.matrix_id!=? ", - [roomID, client.userID]); - if (rs.length > 0) { - displayname = ""; - for (var i = 0; i < rs.length; i++) { - String username = rs[i]["displayname"]; - if (username == "" || username == null) username = rs[i]["matrix_id"]; - if (rs[i]["state_key"] != client.userID) displayname += username + ", "; - } - if (displayname == "" || displayname == null) - displayname = 'Empty chat'; - else - displayname = displayname.substring(0, displayname.length - 2); - } - return displayname; - } - - /// Returns the (first) room ID from the store which is a private chat with - /// the user [userID]. Returns null if there is none. - Future getDirectChatRoomID(String userID) async { - List> res = await db.rawQuery( - "SELECT id FROM Rooms WHERE direct_chat_matrix_id=? AND membership!='leave' LIMIT 1", - [userID]); - if (res.length != 1) return null; - return res[0]["id"]; - } - - /// Returns the power level of the user for the given [roomID]. Returns null if - /// the room or the own user wasn't found. - Future getPowerLevel(String roomID) async { - List> res = await db.rawQuery( - "SELECT power_level FROM Users WHERE matrix_id=? AND chat_id=?", - [roomID, client.userID]); - if (res.length != 1) return null; - return res[0]["power_level"]; - } - - /// Returns the power levels from all users for the given [roomID]. - Future> getPowerLevels(String roomID) async { - List> res = await db.rawQuery( - "SELECT matrix_id, power_level FROM Users WHERE chat_id=?", - [roomID, client.userID]); - Map powerMap = {}; - for (int i = 0; i < res.length; i++) - powerMap[res[i]["matrix_id"]] = res[i]["power_level"]; - return powerMap; - } - - Future>> getAccountDataDirectChats() async { - Map> directChats = {}; - List> res = await db.rawQuery( - "SELECT id, direct_chat_matrix_id FROM Rooms WHERE direct_chat_matrix_id!=''"); - for (int i = 0; i < res.length; i++) { - if (directChats.containsKey(res[i]["direct_chat_matrix_id"])) - directChats[res[i]["direct_chat_matrix_id"]].add(res[i]["id"]); - else - directChats[res[i]["direct_chat_matrix_id"]] = [res[i]["id"]]; - } - return directChats; - } - Future forgetRoom(String roomID) async { await db.rawDelete("DELETE FROM Rooms WHERE id=?", [roomID]); return; @@ -492,10 +386,9 @@ class Store { /// Searches for the event in the store. Future getEventById(String eventID, Room room) async { List> res = await db.rawQuery( - "SELECT * FROM Events WHERE id=? AND chat_id=?", [eventID, room.id]); + "SELECT * FROM Events WHERE id=? AND room_id=?", [eventID, room.id]); if (res.length == 0) return null; - return Event.fromJson(res[0], room, - senderUser: (await room.getUserByMXID(res[0]["sender"]))); + return Event.fromJson(res[0], room); } Future forgetNotification(String roomID) async { @@ -541,14 +434,9 @@ class Store { 'prev_batch TEXT, ' + 'joined_member_count INTEGER, ' + 'invited_member_count INTEGER, ' + + 'heroes TEXT, ' + 'UNIQUE(id))', - /// The users which can be used to generate a room name if the room does not have one. - 'Heroes': 'CREATE TABLE IF NOT EXISTS Heroes(' + - 'room_id TEXT PRIMARY KEY, ' + - 'matrix_id TEXT, ' + - 'UNIQUE(room_id,matrix_id))', - /// The database scheme for the TimelineEvent class. 'Events': 'CREATE TABLE IF NOT EXISTS Events(' + 'event_id TEXT PRIMARY KEY, ' + From 6cebee9eb7a860acf385bda255d4a7945bbdf655 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 09:50:40 +0200 Subject: [PATCH 07/75] [Room] Also query states for rooms --- lib/src/Room.dart | 6 ++++-- lib/src/Store.dart | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 0f265c56..46bdeef2 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -395,9 +395,11 @@ class Room { return resp; } - /// Returns a Room from a json String which comes normally from the store. + /// Returns a Room from a json String which comes normally from the store. If the + /// state are also given, the method will await them. static Future getRoomFromTableRow( - Map row, Client matrix) async { + Map row, Client matrix, + {Future>> states}) async { String avatarUrl = row["avatar_url"]; if (avatarUrl == "") avatarUrl = await matrix.store?.getAvatarFromSingleChat(row["id"]) ?? ""; diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 116a3f26..cb50bd82 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -363,8 +363,8 @@ class Store { " ORDER BY origin_server_ts DESC "); List roomList = []; for (num i = 0; i < res.length; i++) { - Room room = await Room.getRoomFromTableRow( - res[i], client); // TODO: Also query the states for a room + Room room = await Room.getRoomFromTableRow(res[i], client, + states: getStatesFromRoomId(res[i]["id"])); roomList.add(room); } return roomList; @@ -375,7 +375,12 @@ class Store { List> res = await db.rawQuery("SELECT * FROM Rooms WHERE id=?", [id]); if (res.length != 1) return null; - return Room.getRoomFromTableRow(res[0], client); + return Room.getRoomFromTableRow(res[0], client, + states: getStatesFromRoomId(id)); + } + + Future>> getStatesFromRoomId(String id) async { + return db.rawQuery("SELECT * FROM States WHERE room_id=?", [id]); } Future forgetRoom(String roomID) async { From 8e7eaac9d2dcc3e26aa1d4ed11df296df0d16877 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 09:52:36 +0200 Subject: [PATCH 08/75] [Event] New event classes --- lib/src/Event.dart | 207 +++++++++--------------------------------- lib/src/RawEvent.dart | 163 +++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 163 deletions(-) create mode 100644 lib/src/RawEvent.dart diff --git a/lib/src/Event.dart b/lib/src/Event.dart index c5fb1ab6..7f969ff2 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -21,37 +21,14 @@ * along with famedlysdk. If not, see . */ -import 'dart:convert'; - -import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; -import './User.dart'; - -/// A single Matrix event, e.g. a message in a chat. -class Event { - /// The Matrix ID for this event in the format '$localpart:server.abc'. - final String id; - - /// The room this event belongs to. - final Room room; - - /// The time this event has received at the server. - final ChatTime time; - - /// The user who has sent this event. - final User sender; - - /// The user who is the target of this event e.g. for a m.room.member event. - final User stateKey; - - /// The type of this event. Mostly this is 'timeline'. - final String environment; - - Event replyEvent; +import './RawEvent.dart'; +/// Defines a timeline event for a room. +class Event extends RawEvent { /// The status of this event. /// -1=ERROR /// 0=SENDING @@ -59,20 +36,44 @@ class Event { /// 2=RECEIVED int status; - /// The json payload of the content. The content highly depends on the type. - final Map content; - Event( - this.id, - this.sender, - this.time, { - this.room, - this.stateKey, - this.status = 2, - this.environment, - this.content, - this.replyEvent, - }); + {this.status, + dynamic content, + String typeKey, + String eventId, + String roomId, + String sender, + ChatTime time, + dynamic unsigned, + Room room}) + : super( + content: content, + typeKey: typeKey, + eventId: eventId, + roomId: roomId, + sender: sender, + time: time, + unsigned: unsigned, + room: room); + + /// Get a State event from a table row or from the event stream. + factory Event.fromJson( + Map jsonPayload, int status, Room room) { + final Map content = + RawEvent.getMapFromPayload(jsonPayload['content']); + final Map unsigned = + RawEvent.getMapFromPayload(jsonPayload['unsigned']); + return Event( + status: status, + content: content, + typeKey: jsonPayload['type'], + eventId: jsonPayload['event_id'], + roomId: jsonPayload['room_id'], + sender: jsonPayload['sender'], + time: ChatTime(jsonPayload['origin_server_ts']), + unsigned: unsigned, + room: room); + } /// Returns the body of this event if it has a body. String get text => content["body"] ?? ""; @@ -84,89 +85,7 @@ class Event { String getBody() { if (text != "") return text; if (formattedText != "") return formattedText; - return "*** Unable to parse Content ***"; - } - - /// Get the real type. - EventTypes get type { - switch (environment) { - case "m.room.avatar": - return EventTypes.RoomAvatar; - case "m.room.name": - return EventTypes.RoomName; - case "m.room.topic": - return EventTypes.RoomTopic; - case "m.room.Aliases": - return EventTypes.RoomAliases; - case "m.room.canonical_alias": - return EventTypes.RoomCanonicalAlias; - case "m.room.create": - return EventTypes.RoomCreate; - case "m.room.join_rules": - return EventTypes.RoomJoinRules; - case "m.room.member": - return EventTypes.RoomMember; - case "m.room.power_levels": - return EventTypes.RoomPowerLevels; - case "m.room.guest_access": - return EventTypes.GuestAccess; - case "m.room.history_visibility": - return EventTypes.HistoryVisibility; - case "m.room.message": - switch (content["msgtype"] ?? "m.text") { - case "m.text": - if (content.containsKey("m.relates_to")) { - return EventTypes.Reply; - } - return EventTypes.Text; - case "m.notice": - return EventTypes.Notice; - case "m.emote": - return EventTypes.Emote; - case "m.image": - return EventTypes.Image; - case "m.video": - return EventTypes.Video; - case "m.audio": - return EventTypes.Audio; - case "m.file": - return EventTypes.File; - case "m.location": - return EventTypes.Location; - } - } - return EventTypes.Unknown; - } - - /// Generate a new Event object from a json string, mostly a table row. - static Event fromJson(Map jsonObj, Room room, - {User senderUser, User stateKeyUser}) { - Map content = jsonObj["content"]; - - if (content == null && jsonObj["content_json"] != null) - try { - content = json.decode(jsonObj["content_json"]); - } catch (e) { - if (room.client.debug) { - print("jsonObj decode of event content failed: ${e.toString()}"); - } - content = {}; - } - else if (content == null) content = {}; - - if (senderUser == null) senderUser = User.fromJson(jsonObj, room); - if (stateKeyUser == null) stateKeyUser = User(jsonObj["state_key"]); - - return Event( - jsonObj["event_id"] ?? jsonObj["id"], - senderUser, - ChatTime(jsonObj["origin_server_ts"]), - stateKey: stateKeyUser, - environment: jsonObj["type"], - status: jsonObj["status"] ?? 2, - content: content, - room: room, - ); + return "$type"; } /// Removes this event if the status is < 1. This event will just be removed @@ -175,14 +94,14 @@ class Event { if (status < 1) { if (room.client.store != null) await room.client.store.db - .rawDelete("DELETE FROM Events WHERE id=?", [id]); + .rawDelete("DELETE FROM Events WHERE id=?", [eventId]); room.client.connection.onEvent.add(EventUpdate( roomID: room.id, type: "timeline", - eventType: environment, + eventType: typeKey, content: { - "event_id": id, + "event_id": eventId, "status": -2, "content": {"body": "Removed..."} })); @@ -198,42 +117,4 @@ class Event { final String eventID = await room.sendTextEvent(text, txid: txid); return eventID; } - - @Deprecated("Use [client.store.getEventList(Room room)] instead!") - static Future> getEventList(Client matrix, Room room) async { - List eventList = await matrix.store.getEventList(room); - return eventList; - } } - -enum EventTypes { - Text, - Emote, - Notice, - Image, - Video, - Audio, - File, - Location, - Reply, - RoomAliases, - RoomCanonicalAlias, - RoomCreate, - RoomJoinRules, - RoomMember, - RoomPowerLevels, - RoomName, - RoomTopic, - RoomAvatar, - GuestAccess, - HistoryVisibility, - Unknown, -} - -final Map StatusTypes = { - "REMOVE": -2, - "ERROR": -1, - "SENDING": 0, - "SENT": 1, - "RECEIVED": 2, -}; diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart new file mode 100644 index 00000000..153463d3 --- /dev/null +++ b/lib/src/RawEvent.dart @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +import 'dart:convert'; +import 'package:meta/meta.dart'; +import 'package:famedlysdk/src/utils/ChatTime.dart'; +import './Room.dart'; + +class RawEvent { + /// The Matrix ID for this event in the format '$localpart:server.abc'. Please not + /// that account data, presence and other events may not have an eventId. + final String eventId; + + /// The json payload of the content. The content highly depends on the type. + final Map content; + + /// The type String of this event. For example 'm.room.message'. + final String typeKey; + + /// The ID of the room this event belongs to if there is any. Use this to get the room + /// if the [room] is null. + final String roomId; + + /// The user who has sent this event if it is not a global account data event. + final String sender; + + /// The time this event has received at the server. May be null for events like + /// account data. + final ChatTime time; + + /// Optional additional content for this event. + final Map unsigned; + + /// The room this event belongs to. May be null. + final Room room; + + RawEvent( + {@required this.content, + @required this.typeKey, + this.eventId, + this.roomId, + this.sender, + this.time, + this.unsigned, + this.room}); + + static Map getMapFromPayload(dynamic payload) => + payload is String + ? json.decode(payload) + : payload is Map ? payload : null; + + /// Get a State event from a table row or from the event stream. + factory RawEvent.fromJson(Map jsonPayload, Room room) { + final Map content = + getMapFromPayload(jsonPayload['content']); + final Map unsigned = + getMapFromPayload(jsonPayload['unsigned']); + return RawEvent( + content: content, + typeKey: jsonPayload['type'], + eventId: jsonPayload['event_id'], + roomId: jsonPayload['room_id'], + sender: jsonPayload['sender'], + time: ChatTime(jsonPayload['origin_server_ts']), + unsigned: unsigned, + room: room); + } + + /// Get the real type. + EventTypes get type { + switch (typeKey) { + case "m.room.avatar": + return EventTypes.RoomAvatar; + case "m.room.name": + return EventTypes.RoomName; + case "m.room.topic": + return EventTypes.RoomTopic; + case "m.room.Aliases": + return EventTypes.RoomAliases; + case "m.room.canonical_alias": + return EventTypes.RoomCanonicalAlias; + case "m.room.create": + return EventTypes.RoomCreate; + case "m.room.join_rules": + return EventTypes.RoomJoinRules; + case "m.room.member": + return EventTypes.RoomMember; + case "m.room.power_levels": + return EventTypes.RoomPowerLevels; + case "m.room.guest_access": + return EventTypes.GuestAccess; + case "m.room.history_visibility": + return EventTypes.HistoryVisibility; + case "m.room.message": + switch (content["msgtype"] ?? "m.text") { + case "m.text": + if (content.containsKey("m.relates_to")) { + return EventTypes.Reply; + } + return EventTypes.Text; + case "m.notice": + return EventTypes.Notice; + case "m.emote": + return EventTypes.Emote; + case "m.image": + return EventTypes.Image; + case "m.video": + return EventTypes.Video; + case "m.audio": + return EventTypes.Audio; + case "m.file": + return EventTypes.File; + case "m.location": + return EventTypes.Location; + } + } + return EventTypes.Unknown; + } +} + +enum EventTypes { + Text, + Emote, + Notice, + Image, + Video, + Audio, + File, + Location, + Reply, + RoomAliases, + RoomCanonicalAlias, + RoomCreate, + RoomJoinRules, + RoomMember, + RoomPowerLevels, + RoomName, + RoomTopic, + RoomAvatar, + GuestAccess, + HistoryVisibility, + Unknown, +} From 2cea10b01a7b11254a79f4af96309a3c13739a73 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 09:53:22 +0200 Subject: [PATCH 09/75] [State] Add state event class --- lib/src/State.dart | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 lib/src/State.dart diff --git a/lib/src/State.dart b/lib/src/State.dart new file mode 100644 index 00000000..0eaae0a2 --- /dev/null +++ b/lib/src/State.dart @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ +import 'package:famedlysdk/src/utils/ChatTime.dart'; + +import './Room.dart'; +import './RawEvent.dart'; + +class State extends RawEvent { + /// Optional. The previous content for this state. + /// This will be present only for state events appearing in the timeline. + /// If this is not a state event, or there is no previous content, this key will be null. + final Map prevContent; + + /// Optional. This key will only be present for state events. A unique key which defines + /// the overwriting semantics for this piece of room state. + final String stateKey; + + State( + {this.prevContent, + this.stateKey, + dynamic content, + String typeKey, + String eventId, + String roomId, + String sender, + ChatTime time, + dynamic unsigned, + Room room}) + : super( + content: content, + typeKey: typeKey, + eventId: eventId, + roomId: roomId, + sender: sender, + time: time, + unsigned: unsigned, + room: room); + + /// Get a State event from a table row or from the event stream. + factory State.fromJson(Map jsonPayload, Room room) { + final Map content = + RawEvent.getMapFromPayload(jsonPayload['content']); + final Map unsigned = + RawEvent.getMapFromPayload(jsonPayload['unsigned']); + final Map prevContent = + RawEvent.getMapFromPayload(jsonPayload['prev_content']); + return State( + stateKey: jsonPayload['state_key'], + prevContent: prevContent, + content: content, + typeKey: jsonPayload['type'], + eventId: jsonPayload['event_id'], + roomId: jsonPayload['room_id'], + sender: jsonPayload['sender'], + time: ChatTime(jsonPayload['origin_server_ts']), + unsigned: unsigned, + room: room); + } + + /// The unique key of this event. For events with a [stateKey], it will be the + /// stateKey. Otherwise it will be the [type] as a string. + String get key => stateKey != null || stateKey.isEmpty ? type : stateKey; +} From 5184f342518bd62c456bac185c212ba998c61180 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 09:54:08 +0200 Subject: [PATCH 10/75] [User] Rewrite User class --- lib/src/User.dart | 72 ++++++++--------------------------------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/lib/src/User.dart b/lib/src/User.dart index a45d101f..45b0ed73 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -22,6 +22,7 @@ */ import 'package:famedlysdk/src/Room.dart'; +import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; import 'package:famedlysdk/src/utils/MxContent.dart'; @@ -30,85 +31,34 @@ import 'Connection.dart'; enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. -class User { +class User extends State { /// The full qualified Matrix ID in the format @username:server.abc. - final String id; + String get id => stateKey; /// The displayname of the user if the user has set one. - final String displayName; + String get displayName => content["displayname"]; /// The membership status of the user. One of: /// join /// invite /// leave /// ban - Membership membership; + Membership get membership => Membership.values.firstWhere((e) { + if (content["membership"] != null) { + return e.toString() == 'Membership.' + content['membership']; + } + return false; + }); /// The avatar if the user has one. MxContent avatarUrl; - /// The powerLevel of the user. Normally: - /// 0=Normal user - /// 50=Moderator - /// 100=Admin - int powerLevel = 0; - - /// All users normally belong to a room. - final Room room; - - @Deprecated("Use membership instead!") - String get status => membership.toString().split('.').last; - - @Deprecated("Use ID instead!") - String get mxid => id; - - @Deprecated("Use avatarUrl instead!") - MxContent get avatar_url => avatarUrl; - - User( - String id, { - this.membership, - this.displayName, - this.avatarUrl, - this.powerLevel, - this.room, - }) : this.id = id ?? ""; - /// Returns the displayname or the local part of the Matrix ID if the user /// has no displayname. String calcDisplayname() => (displayName == null || displayName.isEmpty) ? id.replaceFirst("@", "").split(":")[0] : displayName; - /// Creates a new User object from a json string like a row from the database. - static User fromJson(Map json, Room room) { - return User(json['matrix_id'] ?? json['sender'], - displayName: json['displayname'], - avatarUrl: MxContent(json['avatar_url']), - membership: Membership.values.firstWhere((e) { - if (json["membership"] != null) { - return e.toString() == 'Membership.' + json['membership']; - } - return false; - }, orElse: () => null), - powerLevel: json['power_level'], - room: room); - } - - /// Checks if the client's user has the permission to kick this user. - Future get canKick async { - final int ownPowerLevel = await room.client.store.getPowerLevel(room.id); - return ownPowerLevel > powerLevel && - ownPowerLevel >= room.powerLevels["power_kick"]; - } - - /// Checks if the client's user has the permission to ban or unban this user. - Future get canBan async { - final int ownPowerLevel = await room.client.store.getPowerLevel(room.id); - return ownPowerLevel > powerLevel && - ownPowerLevel >= room.powerLevels["power_ban"]; - } - /// Call the Matrix API to kick this user from this room. Future kick() async { dynamic res = await room.kick(id); @@ -137,7 +87,7 @@ class User { /// Returns null on error. Future startDirectChat() async { // Try to find an existing direct chat - String roomID = await room.client?.store?.getDirectChatRoomID(id); + String roomID = await room.client?.rooms.getDirectChatRoomID(id); if (roomID != null) return roomID; // Start a new direct chat From d9af551555ebfd03fcf3ac1d6de3cd6a4e9aaeec Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 10:17:03 +0200 Subject: [PATCH 11/75] [Room] Refactor Room --- lib/src/Room.dart | 150 ++++++++++++++++++---------------------------- lib/src/User.dart | 2 + 2 files changed, 59 insertions(+), 93 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 46bdeef2..e62538ff 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -23,6 +23,7 @@ import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; +import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; @@ -40,15 +41,6 @@ class Room { /// Membership status of the user for this room. Membership membership; - /// The name of the room if set by a participant. - String name; - - /// The topic of the room if set by a participant. - String topic; - - /// The avatar of the room if set by a participant. - MxContent avatar = MxContent(""); - /// The count of unread notifications. int notificationCount; @@ -57,7 +49,11 @@ class Room { String prev_batch; - String draft; + List mHeroes; + int mJoinedMemberCount; + int mInvitedMemberCount; + + Map states; /// Time when the user has last read the chat. ChatTime unread; @@ -65,69 +61,63 @@ class Room { /// ID of the fully read marker event. String fullyRead; + /// The name of the room if set by a participant. + String get name { + if (states["m.room.name"] != null && + !states["m.room.name"].content["name"].isEmpty) + return states["m.room.name"].content["name"]; + if (canonicalAlias != null && !canonicalAlias.isEmpty) + return canonicalAlias.substring(1, canonicalAlias.length).split(":")[0]; + if (mHeroes.length > 0) { + String displayname = ""; + for (int i = 0; i < mHeroes.length; i++) + displayname += User(mHeroes[i]).calcDisplayname() + ", "; + return displayname.substring(0, displayname.length - 2); + } + return "Empty chat"; + } + + /// The topic of the room if set by a participant. + String get topic => states["m.room.topic"] != null + ? states["m.room.topic"].content["topic"] + : ""; + + /// The avatar of the room if set by a participant. + MxContent get avatar { + if (states["m.room.avatar"] != null) + return MxContent(states["m.room.avatar"].content["avatar_url"]); + if (mHeroes.length == 1) return getUserByMXID(mHeroes[0]).avatar; + return MxContent(""); + } + /// The address in the format: #roomname:homeserver.org. - String canonicalAlias; + String get canonicalAlias => states["m.room.canonical_alias"] != null + ? states["m.room.canonical_alias"].content["canonical_alias"] + : ""; /// If this room is a direct chat, this is the matrix ID of the user - String directChatMatrixID; + String get directChatMatrixID => ""; // TODO: Needs account_data in client /// Must be one of [all, mention] String notificationSettings; - /// Are guest users allowed? - String guestAccess; - - /// Who can see the history of this room? - String historyVisibility; - - /// Who is allowed to join this room? - String joinRules; - - /// The needed power levels for all actions. - Map powerLevels = {}; - - List mHeroes; - int mJoinedMemberCount; - int mInvitedMemberCount; - - Event lastEvent; + Event get lastEvent => states["m.room.message"] as Event; /// Your current client instance. final Client client; - @Deprecated("Rooms.roomID is deprecated! Use Rooms.id instead!") - String get roomID => this.id; - - @Deprecated("Rooms.matrix is deprecated! Use Rooms.client instead!") - Client get matrix => this.client; - - @Deprecated("Rooms.status is deprecated! Use Rooms.membership instead!") - String get status => this.membership.toString().split('.').last; - Room({ this.id, this.membership, - this.name, - this.topic, - this.avatar, this.notificationCount, this.highlightCount, this.prev_batch = "", - this.draft, - this.unread, - this.fullyRead, - this.canonicalAlias, - this.directChatMatrixID, - this.notificationSettings, - this.guestAccess, - this.historyVisibility, - this.joinRules, - this.powerLevels, - this.lastEvent, this.client, + this.notificationSettings, this.mHeroes, this.mInvitedMemberCount, this.mJoinedMemberCount, + this.states, }); /// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and @@ -400,50 +390,29 @@ class Room { static Future getRoomFromTableRow( Map row, Client matrix, {Future>> states}) async { - String avatarUrl = row["avatar_url"]; - if (avatarUrl == "") - avatarUrl = await matrix.store?.getAvatarFromSingleChat(row["id"]) ?? ""; - - return Room( + Room newRoom = Room( id: row["id"], - name: row["topic"], - membership: Membership.values - .firstWhere((e) => e.toString() == 'Membership.' + row["membership"]), - topic: row["description"], - avatar: MxContent(avatarUrl), notificationCount: row["notification_count"], highlightCount: row["highlight_count"], - unread: ChatTime(row["unread"]), - fullyRead: row["fully_read"], notificationSettings: row["notification_settings"], - directChatMatrixID: row["direct_chat_matrix_id"], - draft: row["draft"], prev_batch: row["prev_batch"], - guestAccess: row["guest_access"], - historyVisibility: row["history_visibility"], - joinRules: row["join_rules"], - canonicalAlias: row["canonical_alias"], mInvitedMemberCount: row["invited_member_count"], mJoinedMemberCount: row["joined_member_count"], mHeroes: row["heroes"]?.split(",") ?? [], - powerLevels: { - "power_events_default": row["power_events_default"], - "power_state_default": row["power_state_default"], - "power_redact": row["power_redact"], - "power_invite": row["power_invite"], - "power_ban": row["power_ban"], - "power_kick": row["power_kick"], - "power_user_default": row["power_user_default"], - "power_event_avatar": row["power_event_avatar"], - "power_event_history_visibility": row["power_event_history_visibility"], - "power_event_canonical_alias": row["power_event_canonical_alias"], - "power_event_aliases": row["power_event_aliases"], - "power_event_name": row["power_event_name"], - "power_event_power_levels": row["power_event_power_levels"], - }, - lastEvent: Event.fromJson(row, null), client: matrix, ); + + Map newStates = {}; + if (states != null) { + List> rawStates = await states; + for (int i = 0; i < rawStates.length; i++) { + State newState = State.fromJson(rawStates[i], newRoom); + newStates[newState.key] = newState; + } + newRoom.states = newStates; + } + + return newRoom; } @Deprecated("Use client.store.getRoomById(String id) instead!") @@ -509,11 +478,7 @@ class Room { } Future getUserByMXID(String mxID) async { - if (client.store != null) { - final User storeEvent = - await client.store.getUser(matrixID: mxID, room: this); - if (storeEvent != null) return storeEvent; - } + if (states[mxID] != null) return states[mxID] as User; final dynamic resp = await client.connection.jsonRequest( type: HTTPType.GET, action: "/client/r0/rooms/$id/state/m.room.member/$mxID"); @@ -533,7 +498,6 @@ class Room { final dynamic resp = await client.connection.jsonRequest( type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID"); if (resp is ErrorResponse) return null; - return Event.fromJson(resp, this, - senderUser: (await getUserByMXID(resp["sender"]))); + return Event.fromJson(resp, this); } } diff --git a/lib/src/User.dart b/lib/src/User.dart index 45b0ed73..260e68f8 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -32,6 +32,8 @@ enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. class User extends State { + User(String sender) : super(sender: sender); + /// The full qualified Matrix ID in the format @username:server.abc. String get id => stateKey; From 7401a0dc2041051763ecc9e524a0d6dccdbf22a8 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 10:32:18 +0200 Subject: [PATCH 12/75] [AccountData] Add new classes --- lib/src/AccountData.dart | 32 ++++++++++++++++++++++++++++++++ lib/src/Presence.dart | 32 ++++++++++++++++++++++++++++++++ lib/src/RawEvent.dart | 3 +-- lib/src/Room.dart | 6 +++++- lib/src/RoomAccountData.dart | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 lib/src/AccountData.dart create mode 100644 lib/src/Presence.dart create mode 100644 lib/src/RoomAccountData.dart diff --git a/lib/src/AccountData.dart b/lib/src/AccountData.dart new file mode 100644 index 00000000..0eb1393c --- /dev/null +++ b/lib/src/AccountData.dart @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +class AccountData { + /// The json payload of the content. The content highly depends on the type. + final Map content; + + /// The type String of this event. For example 'm.room.message'. + final String typeKey; + + AccountData({this.content, this.typeKey}); +} diff --git a/lib/src/Presence.dart b/lib/src/Presence.dart new file mode 100644 index 00000000..291cd04c --- /dev/null +++ b/lib/src/Presence.dart @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +import 'package:famedlysdk/src/AccountData.dart'; + +class Presence extends AccountData { + /// The user who has sent this event if it is not a global account data event. + final String sender; + + Presence({this.sender, Map content, String typeKey}) + : super(content: content, typeKey: typeKey); +} diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 153463d3..f8aeb8bb 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -37,8 +37,7 @@ class RawEvent { /// The type String of this event. For example 'm.room.message'. final String typeKey; - /// The ID of the room this event belongs to if there is any. Use this to get the room - /// if the [room] is null. + /// The ID of the room this event belongs to. final String roomId; /// The user who has sent this event if it is not a global account data event. diff --git a/lib/src/Room.dart b/lib/src/Room.dart index e62538ff..c4b896a5 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -23,6 +23,7 @@ import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; +import 'package:famedlysdk/src/RoomAccountData.dart'; import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; @@ -55,6 +56,8 @@ class Room { Map states; + Map roomAccountData; + /// Time when the user has last read the chat. ChatTime unread; @@ -86,7 +89,8 @@ class Room { MxContent get avatar { if (states["m.room.avatar"] != null) return MxContent(states["m.room.avatar"].content["avatar_url"]); - if (mHeroes.length == 1) return getUserByMXID(mHeroes[0]).avatar; + if (mHeroes.length == 1 && states[mHeroes[0]] != null) + return (states[mHeroes[0]] as User).avatarUrl; return MxContent(""); } diff --git a/lib/src/RoomAccountData.dart b/lib/src/RoomAccountData.dart new file mode 100644 index 00000000..2ca75db7 --- /dev/null +++ b/lib/src/RoomAccountData.dart @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +import 'package:famedlysdk/src/AccountData.dart'; + +class RoomAccountData extends AccountData { + /// The user who has sent this event if it is not a global account data event. + final String room_id; + + RoomAccountData({this.room_id, Map content, String typeKey}) + : super(content: content, typeKey: typeKey); +} From a5fc893a48316a7edc0e45007e4d05328b61d3c8 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 10:46:59 +0200 Subject: [PATCH 13/75] [SDK] Bugfixing --- lib/src/Event.dart | 11 +++++----- lib/src/RawEvent.dart | 9 ++++++--- lib/src/Room.dart | 39 +++++++++++++++--------------------- lib/src/RoomAccountData.dart | 21 +++++++++++++++++-- lib/src/State.dart | 9 ++++++--- lib/src/Store.dart | 8 +++++--- lib/src/User.dart | 2 +- 7 files changed, 58 insertions(+), 41 deletions(-) diff --git a/lib/src/Event.dart b/lib/src/Event.dart index 7f969ff2..eba53c80 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -42,7 +42,7 @@ class Event extends RawEvent { String typeKey, String eventId, String roomId, - String sender, + String senderId, ChatTime time, dynamic unsigned, Room room}) @@ -51,25 +51,24 @@ class Event extends RawEvent { typeKey: typeKey, eventId: eventId, roomId: roomId, - sender: sender, + senderId: senderId, time: time, unsigned: unsigned, room: room); /// Get a State event from a table row or from the event stream. - factory Event.fromJson( - Map jsonPayload, int status, Room room) { + factory Event.fromJson(Map jsonPayload, Room room) { final Map content = RawEvent.getMapFromPayload(jsonPayload['content']); final Map unsigned = RawEvent.getMapFromPayload(jsonPayload['unsigned']); return Event( - status: status, + status: jsonPayload['status'] ?? 1, content: content, typeKey: jsonPayload['type'], eventId: jsonPayload['event_id'], roomId: jsonPayload['room_id'], - sender: jsonPayload['sender'], + senderId: jsonPayload['sender'], time: ChatTime(jsonPayload['origin_server_ts']), unsigned: unsigned, room: room); diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index f8aeb8bb..c4bf7b5f 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -22,6 +22,7 @@ */ import 'dart:convert'; +import 'package:famedlysdk/famedlysdk.dart'; import 'package:meta/meta.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; @@ -41,7 +42,9 @@ class RawEvent { final String roomId; /// The user who has sent this event if it is not a global account data event. - final String sender; + final String senderId; + + User get sender => room.states[senderId] ?? User(senderId); /// The time this event has received at the server. May be null for events like /// account data. @@ -58,7 +61,7 @@ class RawEvent { @required this.typeKey, this.eventId, this.roomId, - this.sender, + this.senderId, this.time, this.unsigned, this.room}); @@ -79,7 +82,7 @@ class RawEvent { typeKey: jsonPayload['type'], eventId: jsonPayload['event_id'], roomId: jsonPayload['room_id'], - sender: jsonPayload['sender'], + senderId: jsonPayload['sender'], time: ChatTime(jsonPayload['origin_server_ts']), unsigned: unsigned, room: room); diff --git a/lib/src/Room.dart b/lib/src/Room.dart index c4b896a5..a44e80e3 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -122,6 +122,7 @@ class Room { this.mInvitedMemberCount, this.mJoinedMemberCount, this.states, + this.roomAccountData, }); /// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and @@ -393,7 +394,8 @@ class Room { /// state are also given, the method will await them. static Future getRoomFromTableRow( Map row, Client matrix, - {Future>> states}) async { + {Future>> states, + Future>> roomAccountData}) async { Room newRoom = Room( id: row["id"], notificationCount: row["notification_count"], @@ -416,22 +418,20 @@ class Room { newRoom.states = newStates; } + Map newRoomAccountData = {}; + if (roomAccountData != null) { + List> rawRoomAccountData = await roomAccountData; + for (int i = 0; i < rawRoomAccountData.length; i++) { + RoomAccountData newData = + RoomAccountData.fromJson(rawRoomAccountData[i], newRoom); + newRoomAccountData[newData.typeKey] = newData; + } + newRoom.roomAccountData = newRoomAccountData; + } + return newRoom; } - @Deprecated("Use client.store.getRoomById(String id) instead!") - static Future getRoomById(String id, Client matrix) async { - Room room = await matrix.store.getRoomById(id); - return room; - } - - /// Load a room from the store including all room events. - static Future loadRoomEvents(String id, Client matrix) async { - Room room = await matrix.store.getRoomById(id); - await room.loadEvents(); - return room; - } - /// Creates a timeline from the store. Returns a [Timeline] object. Future getTimeline( {onTimelineUpdateCallback onUpdate, @@ -467,14 +467,7 @@ class Room { return participants; for (num i = 0; i < res["chunk"].length; i++) { - User newUser = User(res["chunk"][i]["state_key"], - displayName: res["chunk"][i]["content"]["displayname"] ?? "", - membership: Membership.values.firstWhere((e) => - e.toString() == - 'Membership.' + res["chunk"][i]["content"]["membership"] ?? - ""), - avatarUrl: MxContent(res["chunk"][i]["content"]["avatar_url"] ?? ""), - room: this); + User newUser = State.fromJson(res["chunk"][i], this) as User; if (newUser.membership != Membership.leave) participants.add(newUser); } @@ -489,7 +482,7 @@ class Room { if (resp is ErrorResponse) return null; // Somehow we miss the mxid in the response and only get the content of the event. resp["matrix_id"] = mxID; - return User.fromJson(resp, this); + return State.fromJson(resp, this) as User; } /// Searches for the event in the store. If it isn't found, try to request it diff --git a/lib/src/RoomAccountData.dart b/lib/src/RoomAccountData.dart index 2ca75db7..5b1f416b 100644 --- a/lib/src/RoomAccountData.dart +++ b/lib/src/RoomAccountData.dart @@ -21,12 +21,29 @@ * along with famedlysdk. If not, see . */ +import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/AccountData.dart'; +import 'package:famedlysdk/src/RawEvent.dart'; class RoomAccountData extends AccountData { /// The user who has sent this event if it is not a global account data event. - final String room_id; + final String roomId; - RoomAccountData({this.room_id, Map content, String typeKey}) + final Room room; + + RoomAccountData( + {this.roomId, this.room, Map content, String typeKey}) : super(content: content, typeKey: typeKey); + + /// Get a State event from a table row or from the event stream. + factory RoomAccountData.fromJson( + Map jsonPayload, Room room) { + final Map content = + RawEvent.getMapFromPayload(jsonPayload['content']); + return RoomAccountData( + content: content, + typeKey: jsonPayload['type'], + roomId: jsonPayload['room_id'], + room: room); + } } diff --git a/lib/src/State.dart b/lib/src/State.dart index 0eaae0a2..dfd55020 100644 --- a/lib/src/State.dart +++ b/lib/src/State.dart @@ -20,6 +20,7 @@ * You should have received a copy of the GNU General Public License * along with famedlysdk. If not, see . */ +import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; @@ -35,6 +36,8 @@ class State extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; + User get stateKeyUser => room.states[stateKey] ?? User(stateKey); + State( {this.prevContent, this.stateKey, @@ -42,7 +45,7 @@ class State extends RawEvent { String typeKey, String eventId, String roomId, - String sender, + String senderId, ChatTime time, dynamic unsigned, Room room}) @@ -51,7 +54,7 @@ class State extends RawEvent { typeKey: typeKey, eventId: eventId, roomId: roomId, - sender: sender, + senderId: senderId, time: time, unsigned: unsigned, room: room); @@ -71,7 +74,7 @@ class State extends RawEvent { typeKey: jsonPayload['type'], eventId: jsonPayload['event_id'], roomId: jsonPayload['room_id'], - sender: jsonPayload['sender'], + senderId: jsonPayload['sender'], time: ChatTime(jsonPayload['origin_server_ts']), unsigned: unsigned, room: room); diff --git a/lib/src/Store.dart b/lib/src/Store.dart index cb50bd82..1b77729e 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -25,6 +25,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:core'; +import 'package:famedlysdk/src/State.dart'; import 'package:path/path.dart' as p; import 'package:sqflite/sqflite.dart'; @@ -297,7 +298,7 @@ class Store { "SELECT * FROM States WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; - return User.fromJson(res[0], room); + return State.fromJson(res[0], room) as User; } /// Loads all Users in the database to provide a contact list @@ -308,7 +309,8 @@ class Store { [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) - userList.add(User.fromJson(res[i], Room(id: "", client: client))); + userList + .add(State.fromJson(res[i], Room(id: "", client: client)) as User); return userList; } @@ -324,7 +326,7 @@ class Store { List participants = []; for (num i = 0; i < res.length; i++) { - participants.add(User.fromJson(res[i], room)); + participants.add(State.fromJson(res[i], room) as User); } return participants; diff --git a/lib/src/User.dart b/lib/src/User.dart index 260e68f8..61523411 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -32,7 +32,7 @@ enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. class User extends State { - User(String sender) : super(sender: sender); + User(String userId) : super(senderId: userId); /// The full qualified Matrix ID in the format @username:server.abc. String get id => stateKey; From beff1660375a388c0573f54c66cd323f0de44e47 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 11:23:57 +0200 Subject: [PATCH 14/75] [Room] Add powerLevel methods --- lib/src/Room.dart | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index a44e80e3..613fe253 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -497,4 +497,25 @@ class Room { if (resp is ErrorResponse) return null; return Event.fromJson(resp, this); } + + /// Returns the user's own power level. + int get ownPowerLevel { + int powerLevel = 0; + State powerLevelState = states["m.room.power_levels"]; + if (powerLevelState == null) return powerLevel; + if (powerLevelState.content["users_default"] is int) + powerLevel = powerLevelState.content["users_default"]; + if (powerLevelState.content["users"] is Map && + powerLevelState.content["users"][client.userID] != null) + powerLevel = powerLevelState.content["users"][client.userID]; + return powerLevel; + } + + /// Returns the power levels from all users for this room or null if not given. + Map get powerLevels { + State powerLevelState = states["m.room.power_levels"]; + if (powerLevelState.content["users"] is Map) + return powerLevelState.content["users"]; + return null; + } } From ac39be9a1e00df3b97f228ba8e8f37a28b5c55ec Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 11:38:51 +0200 Subject: [PATCH 15/75] [SDK] Refactoring --- lib/src/Client.dart | 2 ++ lib/src/Connection.dart | 14 ++++++++++++++ lib/src/Room.dart | 3 ++- lib/src/RoomList.dart | 40 ++++++---------------------------------- lib/src/Timeline.dart | 32 +++----------------------------- 5 files changed, 27 insertions(+), 64 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 28dcb07c..263b207a 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -86,6 +86,8 @@ class Client { /// Returns the current login state. bool isLogged() => accessToken != null; + RoomList roomList; + /// Checks the supported versions of the Matrix protocol and the supported /// login types. Returns false if the server is not compatible with the /// client. Automatically sets [matrixVersions] and [lazyLoadMembers]. diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index 68cdecfd..4d86f2a9 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -25,6 +25,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:core'; +import 'package:famedlysdk/src/Room.dart'; +import 'package:famedlysdk/src/RoomList.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -149,6 +151,18 @@ class Connection { client.store?.storeClient(); + List rooms = await client.store + ?.getRoomList(onlyLeft: false, onlyGroups: false, onlyDirect: false); + client.roomList = RoomList( + client: client, + onlyLeft: false, + onlyDirect: false, + onlyGroups: false, + onUpdate: null, + onInsert: null, + onRemove: null, + rooms: rooms); + onLoginStateChanged.add(LoginState.logged); _sync(); diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 613fe253..b83682e7 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -294,7 +294,8 @@ class Room { /// Call the Matrix API to unban a banned user from this room. Future setPower(String userID, int power) async { - Map powerMap = await client.store.getPowerLevels(id); + Map powerMap = states["m.room.power_levels"].content["users"]; + if (powerMap == null) return null; powerMap[userID] = power; dynamic res = await client.connection.jsonRequest( diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index c3d1f2db..44ede0f7 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -24,6 +24,8 @@ import 'dart:async'; import 'dart:core'; +import 'package:famedlysdk/src/State.dart'; + import 'Client.dart'; import 'Event.dart'; import 'Room.dart'; @@ -87,7 +89,6 @@ class RoomList { // Add the new chat to the list Room newRoom = Room( id: chatUpdate.id, - name: "", membership: chatUpdate.membership, prev_batch: chatUpdate.prev_batch, highlightCount: chatUpdate.highlight_count, @@ -125,11 +126,6 @@ class RoomList { } void _handleEventUpdate(EventUpdate eventUpdate) { - // Is the event necessary for the chat list? If not, then return - if (!(eventUpdate.type == "timeline" || - eventUpdate.eventType == "m.room.avatar" || - eventUpdate.eventType == "m.room.name")) return; - // Search the room in the rooms num j = 0; for (j = 0; j < rooms.length; j++) { @@ -138,34 +134,10 @@ class RoomList { final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID); if (!found) return; - // Is this an old timeline event? Then stop here... - /*if (eventUpdate.type == "timeline" && - ChatTime(eventUpdate.content["origin_server_ts"]) <= - rooms[j].timeCreated) return;*/ - - if (eventUpdate.type == "timeline") { - User stateKey = null; - if (eventUpdate.content["state_key"] is String) - stateKey = User(eventUpdate.content["state_key"]); - // Update the last message preview - rooms[j].lastEvent = Event( - eventUpdate.content["id"], - User(eventUpdate.content["sender"]), - ChatTime(eventUpdate.content["origin_server_ts"]), - room: rooms[j], - stateKey: stateKey, - content: eventUpdate.content["content"], - environment: eventUpdate.eventType, - status: 2, - ); - } - if (eventUpdate.eventType == "m.room.name") { - // Update the room name - rooms[j].name = eventUpdate.content["content"]["name"]; - } else if (eventUpdate.eventType == "m.room.avatar") { - // Update the room avatar - rooms[j].avatar = MxContent(eventUpdate.content["content"]["url"]); - } + State stateEvent = State.fromJson(eventUpdate.content, rooms[j]); + if (rooms[j].states[stateEvent.key] != null && + rooms[j].states[stateEvent.key].time > stateEvent.time) return; + rooms[j].states[stateEvent.key] = stateEvent; sortAndUpdate(); } diff --git a/lib/src/Timeline.dart b/lib/src/Timeline.dart index 6fd2adb9..36e49079 100644 --- a/lib/src/Timeline.dart +++ b/lib/src/Timeline.dart @@ -47,8 +47,8 @@ class Timeline { int _findEvent({String event_id, String unsigned_txid}) { int i; for (i = 0; i < events.length; i++) { - if (events[i].id == event_id || - (unsigned_txid != null && events[i].id == unsigned_txid)) break; + if (events[i].eventId == event_id || + (unsigned_txid != null && events[i].eventId == unsigned_txid)) break; } return i; } @@ -82,33 +82,7 @@ class Timeline { eventUpdate.content["avatar_url"] = senderUser.avatarUrl.mxc; } - User stateKeyUser; - if (eventUpdate.content.containsKey("state_key")) { - stateKeyUser = await room.client.store?.getUser( - matrixID: eventUpdate.content["state_key"], room: room); - } - - if (senderUser != null && stateKeyUser != null) { - newEvent = Event.fromJson(eventUpdate.content, room, - senderUser: senderUser, stateKeyUser: stateKeyUser); - } else if (senderUser != null) { - newEvent = Event.fromJson(eventUpdate.content, room, - senderUser: senderUser); - } else if (stateKeyUser != null) { - newEvent = Event.fromJson(eventUpdate.content, room, - stateKeyUser: stateKeyUser); - } else { - newEvent = Event.fromJson(eventUpdate.content, room); - } - - // TODO update to type check when https://gitlab.com/famedly/famedlysdk/merge_requests/28/ is merged - if (newEvent.content.containsKey("m.relates_to")) { - Map relates_to = newEvent.content["m.relates_to"]; - if (relates_to.containsKey("m.in_reply_to")) { - newEvent.replyEvent = await room.getEventById(newEvent - .content["m.relates_to"]["m.in_reply_to"]["event_id"]); - } - } + newEvent = Event.fromJson(eventUpdate.content, room); events.insert(0, newEvent); if (onInsert != null) onInsert(0); From 898767875a3d4748695f85a1681a3e789da3e3d7 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 12:06:28 +0200 Subject: [PATCH 16/75] [SDK] Fix bugs --- lib/src/AccountData.dart | 9 +++++++++ lib/src/Client.dart | 36 +++++++++++++++++++++++++++++++++++- lib/src/Connection.dart | 32 ++++++++++++++++++++------------ lib/src/Presence.dart | 11 +++++++++++ lib/src/Room.dart | 3 +-- lib/src/RoomList.dart | 7 +++++++ lib/src/Store.dart | 22 ++++++++++++++++++++++ lib/src/User.dart | 2 +- 8 files changed, 106 insertions(+), 16 deletions(-) diff --git a/lib/src/AccountData.dart b/lib/src/AccountData.dart index 0eb1393c..ccb90594 100644 --- a/lib/src/AccountData.dart +++ b/lib/src/AccountData.dart @@ -21,6 +21,8 @@ * along with famedlysdk. If not, see . */ +import 'package:famedlysdk/src/RawEvent.dart'; + class AccountData { /// The json payload of the content. The content highly depends on the type. final Map content; @@ -29,4 +31,11 @@ class AccountData { final String typeKey; AccountData({this.content, this.typeKey}); + + /// Get a State event from a table row or from the event stream. + factory AccountData.fromJson(Map jsonPayload) { + final Map content = + RawEvent.getMapFromPayload(jsonPayload['content']); + return AccountData(content: content, typeKey: jsonPayload['type']); + } } diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 263b207a..6de66c26 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -24,6 +24,10 @@ import 'dart:async'; import 'dart:core'; +import 'package:famedlysdk/src/AccountData.dart'; +import 'package:famedlysdk/src/Presence.dart'; +import 'package:famedlysdk/src/sync/UserUpdate.dart'; + import 'Connection.dart'; import 'Room.dart'; import 'RoomList.dart'; @@ -86,8 +90,38 @@ class Client { /// Returns the current login state. bool isLogged() => accessToken != null; + /// A list of all rooms the user is participating or invited. RoomList roomList; + /// Key/Value store of account data. + Map accountData = {}; + + /// Presences of users by a given matrix ID + Map presences = {}; + + void handleUserUpdate(UserUpdate userUpdate) { + if (userUpdate.type == "account_data") { + AccountData newAccountData = AccountData.fromJson(userUpdate.content); + accountData[newAccountData.typeKey] = newAccountData; + } + if (userUpdate.type == "presence") { + Presence newPresence = Presence.fromJson(userUpdate.content); + presences[newPresence.typeKey] = newPresence; + } + } + + Map> get directChats => + accountData["m.direct"] != null ? accountData["m.direct"].content : {}; + + /// Returns the (first) room ID from the store which is a private chat with the user [userId]. + /// Returns null if there is none. + String getDirectChatFromUserId(String userId) => + accountData["m.direct"] != null && + accountData["m.direct"].content[userId] is List && + accountData["m.direct"].content[userId].length > 0 + ? accountData["m.direct"].content[userId][0] + : null; + /// Checks the supported versions of the Matrix protocol and the supported /// login types. Returns false if the server is not compatible with the /// client. Automatically sets [matrixVersions] and [lazyLoadMembers]. @@ -227,7 +261,7 @@ class Client { /// defined by the autojoin room feature in Synapse. Future> loadFamedlyContacts() async { List contacts = []; - Room contactDiscoveryRoom = await store + Room contactDiscoveryRoom = roomList .getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}"); if (contactDiscoveryRoom != null) contacts = await contactDiscoveryRoom.requestParticipants(); diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index 4d86f2a9..ab312a9c 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -149,25 +149,33 @@ class Connection { client.lazyLoadMembers = newLazyLoadMembers; client.prevBatch = newPrevBatch; - client.store?.storeClient(); + if (client.store != null) { + client.store.storeClient(); - List rooms = await client.store - ?.getRoomList(onlyLeft: false, onlyGroups: false, onlyDirect: false); - client.roomList = RoomList( - client: client, - onlyLeft: false, - onlyDirect: false, - onlyGroups: false, - onUpdate: null, - onInsert: null, - onRemove: null, - rooms: rooms); + List rooms = await client.store + .getRoomList(onlyLeft: false, onlyGroups: false, onlyDirect: false); + client.roomList = RoomList( + client: client, + onlyLeft: false, + onlyDirect: false, + onlyGroups: false, + onUpdate: null, + onInsert: null, + onRemove: null, + rooms: rooms); + client.accountData = await client.store.getAccountData(); + client.presences = await client.store.getPresences(); + } + + _userEventSub ??= onUserEvent.stream.listen(client.handleUserUpdate); onLoginStateChanged.add(LoginState.logged); _sync(); } + StreamSubscription _userEventSub; + /// Resets all settings and stops the synchronisation. void clear() { client.store?.clear(); diff --git a/lib/src/Presence.dart b/lib/src/Presence.dart index 291cd04c..20145198 100644 --- a/lib/src/Presence.dart +++ b/lib/src/Presence.dart @@ -22,6 +22,7 @@ */ import 'package:famedlysdk/src/AccountData.dart'; +import 'package:famedlysdk/src/RawEvent.dart'; class Presence extends AccountData { /// The user who has sent this event if it is not a global account data event. @@ -29,4 +30,14 @@ class Presence extends AccountData { Presence({this.sender, Map content, String typeKey}) : super(content: content, typeKey: typeKey); + + /// Get a State event from a table row or from the event stream. + factory Presence.fromJson(Map jsonPayload) { + final Map content = + RawEvent.getMapFromPayload(jsonPayload['content']); + return Presence( + content: content, + typeKey: jsonPayload['type'], + sender: jsonPayload['sender']); + } } diff --git a/lib/src/Room.dart b/lib/src/Room.dart index b83682e7..04fa9c54 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -363,8 +363,7 @@ class Room { /// Sets this room as a direct chat for this user. Future addToDirectChat(String userID) async { - Map> directChats = - await client.store.getAccountDataDirectChats(); + Map> directChats = client.directChats; if (directChats.containsKey(userID)) if (!directChats[userID].contains(id)) directChats[userID].add(id); else diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 44ede0f7..1a160b51 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -73,6 +73,13 @@ class RoomList { roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate); } + Room getRoomByAlias(String alias) { + for (int i = 0; i < rooms.length; i++) { + if (rooms[i].canonicalAlias == alias) return rooms[i]; + } + return null; + } + void _handleRoomUpdate(RoomUpdate chatUpdate) { // Update the chat list item. // Search the room in the rooms diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 1b77729e..5a2bb9b6 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -25,6 +25,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:core'; +import 'package:famedlysdk/src/AccountData.dart'; +import 'package:famedlysdk/src/Presence.dart'; import 'package:famedlysdk/src/State.dart'; import 'package:path/path.dart' as p; import 'package:sqflite/sqflite.dart'; @@ -398,6 +400,26 @@ class Store { return Event.fromJson(res[0], room); } + Future> getAccountData() async { + Map newAccountData = {}; + List> rawAccountData = + await db.rawQuery("SELECT * FROM AccountData"); + for (int i = 0; i < rawAccountData.length; i++) + newAccountData[rawAccountData[i]["type"]] = + AccountData.fromJson(rawAccountData[i]); + return newAccountData; + } + + Future> getPresences() async { + Map newPresences = {}; + List> rawPresences = + await db.rawQuery("SELECT * FROM Presences"); + for (int i = 0; i < rawPresences.length; i++) + newPresences[rawPresences[i]["type"]] = + Presence.fromJson(rawPresences[i]); + return newPresences; + } + Future forgetNotification(String roomID) async { await db .rawDelete("DELETE FROM NotificationsCache WHERE chat_id=?", [roomID]); diff --git a/lib/src/User.dart b/lib/src/User.dart index 61523411..7a10ab0b 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -89,7 +89,7 @@ class User extends State { /// Returns null on error. Future startDirectChat() async { // Try to find an existing direct chat - String roomID = await room.client?.rooms.getDirectChatRoomID(id); + String roomID = await room.client?.getDirectChatFromUserId(id); if (roomID != null) return roomID; // Start a new direct chat From cacf7cc530c824962973106184e80f60b004f183 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 7 Aug 2019 12:27:02 +0200 Subject: [PATCH 17/75] [Tests] Fix some tests --- lib/src/Connection.dart | 23 ++++++++++++----------- lib/src/Room.dart | 6 +++--- lib/src/RoomList.dart | 2 ++ lib/src/State.dart | 14 +++++++++++++- test/Client_test.dart | 3 +++ test/Event_test.dart | 24 +++++++----------------- 6 files changed, 40 insertions(+), 32 deletions(-) diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index ab312a9c..adc202e2 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -149,24 +149,25 @@ class Connection { client.lazyLoadMembers = newLazyLoadMembers; client.prevBatch = newPrevBatch; + List rooms = []; if (client.store != null) { client.store.storeClient(); - - List rooms = await client.store + rooms = await client.store .getRoomList(onlyLeft: false, onlyGroups: false, onlyDirect: false); - client.roomList = RoomList( - client: client, - onlyLeft: false, - onlyDirect: false, - onlyGroups: false, - onUpdate: null, - onInsert: null, - onRemove: null, - rooms: rooms); client.accountData = await client.store.getAccountData(); client.presences = await client.store.getPresences(); } + client.roomList = RoomList( + client: client, + onlyLeft: false, + onlyDirect: false, + onlyGroups: false, + onUpdate: null, + onInsert: null, + onRemove: null, + rooms: rooms); + _userEventSub ??= onUserEvent.stream.listen(client.handleUserUpdate); onLoginStateChanged.add(LoginState.logged); diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 04fa9c54..4d9e5b26 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -54,9 +54,9 @@ class Room { int mJoinedMemberCount; int mInvitedMemberCount; - Map states; + Map states = {}; - Map roomAccountData; + Map roomAccountData = {}; /// Time when the user has last read the chat. ChatTime unread; @@ -105,7 +105,7 @@ class Room { /// Must be one of [all, mention] String notificationSettings; - Event get lastEvent => states["m.room.message"] as Event; + Event get lastEvent => states["m.room.message"]?.timelineEvent; /// Your current client instance. final Client client; diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 1a160b51..46aac04f 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -103,6 +103,8 @@ class RoomList { mHeroes: chatUpdate.summary?.mHeroes, mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount, mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount, + states: {}, + roomAccountData: {}, ); rooms.insert(position, newRoom); if (onInsert != null) onInsert(position); diff --git a/lib/src/State.dart b/lib/src/State.dart index dfd55020..15b8eef1 100644 --- a/lib/src/State.dart +++ b/lib/src/State.dart @@ -80,7 +80,19 @@ class State extends RawEvent { room: room); } + Event get timelineEvent => Event( + content: content, + typeKey: typeKey, + eventId: eventId, + room: room, + roomId: roomId, + senderId: senderId, + time: time, + unsigned: unsigned, + status: 1, + ); + /// The unique key of this event. For events with a [stateKey], it will be the /// stateKey. Otherwise it will be the [type] as a string. - String get key => stateKey != null || stateKey.isEmpty ? type : stateKey; + String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey; } diff --git a/test/Client_test.dart b/test/Client_test.dart index 40460393..3cb89782 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -107,6 +107,9 @@ void main() { expect(loginState, LoginState.logged); expect(firstSync, true); expect(sync["next_batch"] == matrix.prevBatch, true); + + expect(matrix.accountData.length, 1); + expect(matrix.presences.length, 0); }); test('Try to get ErrorResponse', () async { diff --git a/test/Event_test.dart b/test/Event_test.dart index a6356591..1e501b6c 100644 --- a/test/Event_test.dart +++ b/test/Event_test.dart @@ -24,8 +24,7 @@ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/src/Event.dart'; -import 'package:famedlysdk/src/User.dart'; +import 'package:famedlysdk/src/RawEvent.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -36,9 +35,6 @@ void main() { final int timestamp = DateTime.now().millisecondsSinceEpoch; final String id = "!4fsdfjisjf:server.abc"; final String senderID = "@alice:server.abc"; - final String senderDisplayname = "Alice"; - final String empty = ""; - final Membership membership = Membership.join; final String type = "m.room.message"; final String msgtype = "m.text"; final String body = "Hello World"; @@ -49,24 +45,18 @@ void main() { Map jsonObj = { "event_id": id, - "matrix_id": senderID, - "displayname": senderDisplayname, - "avatar_url": empty, - "membership": membership.toString().split('.').last, + "sender": senderID, "origin_server_ts": timestamp, - "state_key": empty, "type": type, - "content_json": contentJson, + "status": 2, + "content": contentJson, }; test("Create from json", () async { Event event = Event.fromJson(jsonObj, null); - expect(event.id, id); - expect(event.sender.id, senderID); - expect(event.sender.displayName, senderDisplayname); - expect(event.sender.avatarUrl.mxc, empty); - expect(event.sender.membership, membership); + expect(event.eventId, id); + expect(event.senderId, senderID); expect(event.status, 2); expect(event.text, body); expect(event.formattedText, formatted_body); @@ -121,7 +111,7 @@ void main() { expect(event.type, EventTypes.HistoryVisibility); jsonObj["type"] = "m.room.message"; - jsonObj["content"] = json.decode(jsonObj["content_json"]); + jsonObj["content"] = json.decode(jsonObj["content"]); jsonObj["content"]["msgtype"] = "m.notice"; event = Event.fromJson(jsonObj, null); From bb41db7f142dcfd2f9ee7e8d83cdc82dc07c0597 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 09:58:37 +0200 Subject: [PATCH 18/75] [Tests] Bugfixes --- lib/src/Client.dart | 6 +++--- lib/src/Room.dart | 2 +- lib/src/RoomList.dart | 1 + test/Client_test.dart | 22 ++++++++++++++-------- test/FakeMatrixApi.dart | 40 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 6de66c26..c25f59d0 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -110,14 +110,14 @@ class Client { } } - Map> get directChats => + Map get directChats => accountData["m.direct"] != null ? accountData["m.direct"].content : {}; /// Returns the (first) room ID from the store which is a private chat with the user [userId]. /// Returns null if there is none. String getDirectChatFromUserId(String userId) => accountData["m.direct"] != null && - accountData["m.direct"].content[userId] is List && + accountData["m.direct"].content[userId] is List && accountData["m.direct"].content[userId].length > 0 ? accountData["m.direct"].content[userId][0] : null; @@ -266,7 +266,7 @@ class Client { if (contactDiscoveryRoom != null) contacts = await contactDiscoveryRoom.requestParticipants(); else - contacts = await store.loadContacts(); + contacts = await store?.loadContacts(); return contacts; } diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 4d9e5b26..f9de44bc 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -96,7 +96,7 @@ class Room { /// The address in the format: #roomname:homeserver.org. String get canonicalAlias => states["m.room.canonical_alias"] != null - ? states["m.room.canonical_alias"].content["canonical_alias"] + ? states["m.room.canonical_alias"].content["alias"] : ""; /// If this room is a direct chat, this is the matrix ID of the user diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 46aac04f..b8079556 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -105,6 +105,7 @@ class RoomList { mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount, states: {}, roomAccountData: {}, + client: client, ); rooms.insert(position, newRoom); if (onInsert != null) onInsert(position); diff --git a/test/Client_test.dart b/test/Client_test.dart index 3cb89782..11183ae5 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -62,9 +62,9 @@ void main() { matrix.connection.onError.stream.first; final bool checkResp1 = - await matrix.checkServer("https://fakeServer.wrongaddress"); + await matrix.checkServer("https://fakeserver.wrongaddress"); final bool checkResp2 = - await matrix.checkServer("https://fakeServer.notExisting"); + await matrix.checkServer("https://fakeserver.notexisting"); ErrorResponse checkError = await errorFuture; @@ -108,8 +108,17 @@ void main() { expect(firstSync, true); expect(sync["next_batch"] == matrix.prevBatch, true); - expect(matrix.accountData.length, 1); - expect(matrix.presences.length, 0); + expect(matrix.accountData.length, 2); + expect(matrix.getDirectChatFromUserId("@bob:example.com"), + "!abcdefgh:example.com"); + expect(matrix.directChats, matrix.accountData["m.direct"].content); + expect(matrix.presences.length, 1); + expect(matrix.roomList.rooms.length, 2); + expect(matrix.roomList.rooms[1].canonicalAlias, + "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"); + final List contacts = await matrix.loadFamedlyContacts(); + expect(contacts.length, 1); + expect(contacts[0].senderId, "@alice:example.com"); }); test('Try to get ErrorResponse', () async { @@ -212,16 +221,13 @@ void main() { List eventUpdateList = await userUpdateListFuture; - expect(eventUpdateList.length, 3); + expect(eventUpdateList.length, 4); expect(eventUpdateList[0].eventType == "m.presence", true); expect(eventUpdateList[0].type == "presence", true); expect(eventUpdateList[1].eventType == "org.example.custom.config", true); expect(eventUpdateList[1].type == "account_data", true); - - expect(eventUpdateList[2].eventType == "m.new_device", true); - expect(eventUpdateList[2].type == "to_device", true); }); testWidgets('should get created', create); diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index 5f3e8a96..bda93ad8 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -153,6 +153,24 @@ class FakeMatrixApi extends MockClient { {"type": "m.login.password"} ] }, + "/client/r0/rooms/!726s6s6q:example.com/members": (var req) => { + "chunk": [ + { + "content": { + "membership": "join", + "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", + "displayname": "Alice Margatroid" + }, + "type": "m.room.member", + "event_id": "§143273582443PhrSn:example.org", + "room_id": "!636q39766251:example.com", + "sender": "@alice:example.org", + "origin_server_ts": 1432735824653, + "unsigned": {"age": 1234}, + "state_key": "@alice:example.org" + } + ] + }, "/client/r0/rooms/!localpart:server.abc/members": (var req) => { "chunk": [ { @@ -333,7 +351,16 @@ class FakeMatrixApi extends MockClient { { "type": "org.example.custom.config", "content": {"custom_config_key": "custom_config_value"} - } + }, + { + "content": { + "@bob:example.com": [ + "!abcdefgh:example.com", + "!hgfedcba:example.com" + ] + }, + "type": "m.direct" + }, ] }, "to_device": { @@ -364,6 +391,17 @@ class FakeMatrixApi extends MockClient { "content": {"membership": "join"}, "origin_server_ts": 1417731086795, "event_id": "66697273743031:example.com" + }, + { + "sender": "@alice:example.com", + "type": "m.room.canonical_alias", + "content": { + "alias": + "#famedlyContactDiscovery:fakeServer.notExisting" + }, + "state_key": "", + "origin_server_ts": 1417731086796, + "event_id": "66697273743032:example.com" } ] }, From df0cc1d273c177b92b7df479667eb2b0e1f5bc4a Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 10:31:39 +0200 Subject: [PATCH 19/75] [Tests] Refactoring --- lib/src/Client.dart | 13 ++++++- lib/src/RawEvent.dart | 2 +- lib/src/Room.dart | 25 ++++++++++--- lib/src/RoomList.dart | 3 -- lib/src/State.dart | 14 ++++++- lib/src/User.dart | 24 +++++++++++- lib/src/utils/ChatTime.dart | 1 - test/Client_test.dart | 73 +++++++++++++++++++++++-------------- test/FakeMatrixApi.dart | 2 +- test/Room_test.dart | 6 +-- test/Timeline_test.dart | 13 +++---- test/User_test.dart | 10 ++--- 12 files changed, 128 insertions(+), 58 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index c25f59d0..90561afc 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -99,14 +99,22 @@ class Client { /// Presences of users by a given matrix ID Map presences = {}; + /// Callback will be called on account data updates. + AccountDataEventCB onAccountData; + + /// Callback will be called on presences. + PresenceCB onPresence; + void handleUserUpdate(UserUpdate userUpdate) { if (userUpdate.type == "account_data") { AccountData newAccountData = AccountData.fromJson(userUpdate.content); accountData[newAccountData.typeKey] = newAccountData; + if (onAccountData != null) onAccountData(newAccountData); } if (userUpdate.type == "presence") { Presence newPresence = Presence.fromJson(userUpdate.content); - presences[newPresence.typeKey] = newPresence; + presences[newPresence.sender] = newPresence; + if (onPresence != null) onPresence(newPresence); } } @@ -320,3 +328,6 @@ class Client { return resp; } } + +typedef AccountDataEventCB = void Function(AccountData accountData); +typedef PresenceCB = void Function(Presence presence); diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index c4bf7b5f..8deff6f4 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -44,7 +44,7 @@ class RawEvent { /// The user who has sent this event if it is not a global account data event. final String senderId; - User get sender => room.states[senderId] ?? User(senderId); + User get sender => room.states[senderId] ?? User(senderId: senderId); /// The time this event has received at the server. May be null for events like /// account data. diff --git a/lib/src/Room.dart b/lib/src/Room.dart index f9de44bc..ee376147 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -74,7 +74,7 @@ class Room { if (mHeroes.length > 0) { String displayname = ""; for (int i = 0; i < mHeroes.length; i++) - displayname += User(mHeroes[i]).calcDisplayname() + ", "; + displayname += User(senderId: mHeroes[i]).calcDisplayname() + ", "; return displayname.substring(0, displayname.length - 2); } return "Empty chat"; @@ -99,8 +99,23 @@ class Room { ? states["m.room.canonical_alias"].content["alias"] : ""; - /// If this room is a direct chat, this is the matrix ID of the user - String get directChatMatrixID => ""; // TODO: Needs account_data in client + /// If this room is a direct chat, this is the matrix ID of the user. + /// Returns null otherwise. + String get directChatMatrixID { + String returnUserId = null; + if (client.directChats is Map) { + client.directChats.forEach((String userId, dynamic roomIds) { + if (roomIds is List) { + for (int i = 0; i < roomIds.length; i++) + if (roomIds[i] == this.id) { + returnUserId = userId; + break; + } + } + }); + } + return returnUserId; + } /// Must be one of [all, mention] String notificationSettings; @@ -136,7 +151,7 @@ class Room { if (mHeroes.length > 0) { String displayname = ""; for (int i = 0; i < mHeroes.length; i++) - displayname += User(mHeroes[i]).calcDisplayname() + ", "; + displayname += User(senderId: mHeroes[i]).calcDisplayname() + ", "; return displayname.substring(0, displayname.length - 2); } return "Empty chat"; @@ -467,7 +482,7 @@ class Room { return participants; for (num i = 0; i < res["chunk"].length; i++) { - User newUser = State.fromJson(res["chunk"][i], this) as User; + User newUser = State.fromJson(res["chunk"][i], this).asUser; if (newUser.membership != Membership.leave) participants.add(newUser); } diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index b8079556..6096dd44 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -27,13 +27,10 @@ import 'dart:core'; import 'package:famedlysdk/src/State.dart'; import 'Client.dart'; -import 'Event.dart'; import 'Room.dart'; import 'User.dart'; import 'sync/EventUpdate.dart'; import 'sync/RoomUpdate.dart'; -import 'utils/ChatTime.dart'; -import 'utils/MxContent.dart'; /// Represents a list of rooms for this client, which will automatically update /// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get diff --git a/lib/src/State.dart b/lib/src/State.dart index 15b8eef1..52dee1f0 100644 --- a/lib/src/State.dart +++ b/lib/src/State.dart @@ -36,7 +36,7 @@ class State extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; - User get stateKeyUser => room.states[stateKey] ?? User(stateKey); + User get stateKeyUser => room.states[stateKey] ?? User(senderId: stateKey); State( {this.prevContent, @@ -95,4 +95,16 @@ class State extends RawEvent { /// The unique key of this event. For events with a [stateKey], it will be the /// stateKey. Otherwise it will be the [type] as a string. String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey; + + User get asUser => User( + stateKey: stateKey, + prevContent: prevContent, + content: content, + typeKey: typeKey, + eventId: eventId, + roomId: roomId, + senderId: senderId, + time: time, + unsigned: unsigned, + room: room); } diff --git a/lib/src/User.dart b/lib/src/User.dart index 7a10ab0b..835a982c 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -24,6 +24,7 @@ import 'package:famedlysdk/src/Room.dart'; import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; +import 'package:famedlysdk/src/utils/ChatTime.dart'; import 'package:famedlysdk/src/utils/MxContent.dart'; import 'Connection.dart'; @@ -32,7 +33,28 @@ enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. class User extends State { - User(String userId) : super(senderId: userId); + User( + {dynamic prevContent, + String stateKey, + dynamic content, + String typeKey, + String eventId, + String roomId, + String senderId, + ChatTime time, + dynamic unsigned, + Room room}) + : super( + stateKey: stateKey, + prevContent: prevContent, + content: content, + typeKey: typeKey, + eventId: eventId, + roomId: roomId, + senderId: senderId, + time: time, + unsigned: unsigned, + room: room); /// The full qualified Matrix ID in the format @username:server.abc. String get id => stateKey; diff --git a/lib/src/utils/ChatTime.dart b/lib/src/utils/ChatTime.dart index bd77d6d1..f53c0581 100644 --- a/lib/src/utils/ChatTime.dart +++ b/lib/src/utils/ChatTime.dart @@ -59,7 +59,6 @@ class ChatTime { return toTimeString(); } else if (sameWeek) { switch (dateTime.weekday) { - // TODO: Needs localization case 1: return "Montag"; case 2: diff --git a/test/Client_test.dart b/test/Client_test.dart index 11183ae5..855da000 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -23,8 +23,10 @@ import 'dart:async'; +import 'package:famedlysdk/src/AccountData.dart'; import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Connection.dart'; +import 'package:famedlysdk/src/Presence.dart'; import 'package:famedlysdk/src/User.dart'; import 'package:famedlysdk/src/requests/SetPushersRequest.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; @@ -61,6 +63,15 @@ void main() { Future errorFuture = matrix.connection.onError.stream.first; + int presenceCounter = 0; + int accountDataCounter = 0; + matrix.onPresence = (Presence data) { + presenceCounter++; + }; + matrix.onAccountData = (AccountData data) { + accountDataCounter++; + }; + final bool checkResp1 = await matrix.checkServer("https://fakeserver.wrongaddress"); final bool checkResp2 = @@ -110,7 +121,8 @@ void main() { expect(matrix.accountData.length, 2); expect(matrix.getDirectChatFromUserId("@bob:example.com"), - "!abcdefgh:example.com"); + "!726s6s6q:example.com"); + expect(matrix.roomList.rooms[1].directChatMatrixID, "@bob:example.com"); expect(matrix.directChats, matrix.accountData["m.direct"].content); expect(matrix.presences.length, 1); expect(matrix.roomList.rooms.length, 2); @@ -118,7 +130,11 @@ void main() { "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"); final List contacts = await matrix.loadFamedlyContacts(); expect(contacts.length, 1); - expect(contacts[0].senderId, "@alice:example.com"); + expect(contacts[0].senderId, "@alice:example.org"); + expect( + matrix.presences["@alice:example.com"].content["presence"], "online"); + expect(presenceCounter, 1); + expect(accountDataCounter, 2); }); test('Try to get ErrorResponse', () async { @@ -184,36 +200,39 @@ void main() { List eventUpdateList = await eventUpdateListFuture; - expect(eventUpdateList.length, 7); + expect(eventUpdateList.length, 8); - expect(eventUpdateList[0].eventType == "m.room.member", true); - expect(eventUpdateList[0].roomID == "!726s6s6q:example.com", true); - expect(eventUpdateList[0].type == "state", true); + expect(eventUpdateList[0].eventType, "m.room.member"); + expect(eventUpdateList[0].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[0].type, "state"); - expect(eventUpdateList[1].eventType == "m.room.member", true); - expect(eventUpdateList[1].roomID == "!726s6s6q:example.com", true); - expect(eventUpdateList[1].type == "timeline", true); + expect(eventUpdateList[1].eventType, "m.room.canonical_alias"); + expect(eventUpdateList[1].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[1].type, "state"); - expect(eventUpdateList[2].eventType == "m.room.message", true); - expect(eventUpdateList[2].roomID == "!726s6s6q:example.com", true); - expect(eventUpdateList[2].type == "timeline", true); + expect(eventUpdateList[2].eventType, "m.room.member"); + expect(eventUpdateList[2].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[2].type, "timeline"); - expect(eventUpdateList[3].eventType == "m.tag", true); - expect(eventUpdateList[3].roomID == "!726s6s6q:example.com", true); - expect(eventUpdateList[3].type == "account_data", true); + expect(eventUpdateList[3].eventType, "m.room.message"); + expect(eventUpdateList[3].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[3].type, "timeline"); - expect(eventUpdateList[4].eventType == "org.example.custom.room.config", - true); - expect(eventUpdateList[4].roomID == "!726s6s6q:example.com", true); - expect(eventUpdateList[4].type == "account_data", true); + expect(eventUpdateList[4].eventType, "m.tag"); + expect(eventUpdateList[4].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[4].type, "account_data"); - expect(eventUpdateList[5].eventType == "m.room.name", true); - expect(eventUpdateList[5].roomID == "!696r7674:example.com", true); - expect(eventUpdateList[5].type == "invite_state", true); + expect(eventUpdateList[5].eventType, "org.example.custom.room.config"); + expect(eventUpdateList[5].roomID, "!726s6s6q:example.com"); + expect(eventUpdateList[5].type, "account_data"); - expect(eventUpdateList[6].eventType == "m.room.member", true); - expect(eventUpdateList[6].roomID == "!696r7674:example.com", true); - expect(eventUpdateList[6].type == "invite_state", true); + expect(eventUpdateList[6].eventType, "m.room.name"); + expect(eventUpdateList[6].roomID, "!696r7674:example.com"); + expect(eventUpdateList[6].type, "invite_state"); + + expect(eventUpdateList[7].eventType, "m.room.member"); + expect(eventUpdateList[7].roomID, "!696r7674:example.com"); + expect(eventUpdateList[7].type, "invite_state"); }); test('User Update Test', () async { @@ -244,8 +263,8 @@ void main() { test('createGroup', () async { final List users = [ - User("@alice:fakeServer.notExisting"), - User("@bob:fakeServer.notExisting") + User(senderId: "@alice:fakeServer.notExisting"), + User(senderId: "@bob:fakeServer.notExisting") ]; final String newID = await matrix.createGroup(users); expect(newID, "!1234:fakeServer.notExisting"); diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index bda93ad8..f63f445a 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -355,7 +355,7 @@ class FakeMatrixApi extends MockClient { { "content": { "@bob:example.com": [ - "!abcdefgh:example.com", + "!726s6s6q:example.com", "!hgfedcba:example.com" ] }, diff --git a/test/Room_test.dart b/test/Room_test.dart index b70be7fe..897d1192 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -125,12 +125,8 @@ void main() { expect(room.fullyRead, fullyRead); expect(room.notificationSettings, notificationSettings); expect(room.directChatMatrixID, ""); - expect(room.draft, ""); expect(room.canonicalAlias, canonicalAlias); expect(room.prev_batch, ""); - expect(room.guestAccess, guestAccess); - expect(room.historyVisibility, historyVisibility); - expect(room.joinRules, joinRules); expect(room.lastMessage, body); expect(room.timeCreated.toTimeStamp() >= now, true); room.powerLevels.forEach((String key, int value) { @@ -167,7 +163,7 @@ void main() { test("getEventByID", () async { final Event event = await room.getEventById("1234"); - expect(event.id, "143273582443PhrSn:example.org"); + expect(event.eventId, "143273582443PhrSn:example.org"); }); }); } diff --git a/test/Timeline_test.dart b/test/Timeline_test.dart index b99c6494..3b37c6e7 100644 --- a/test/Timeline_test.dart +++ b/test/Timeline_test.dart @@ -87,10 +87,9 @@ void main() { expect(insertList, [0, 0]); expect(insertList.length, timeline.events.length); expect(timeline.events.length, 2); - expect(timeline.events[0].id, "1"); + expect(timeline.events[0].eventId, "1"); expect(timeline.events[0].sender.id, "@alice:example.com"); expect(timeline.events[0].time.toTimeStamp(), testTimeStamp); - expect(timeline.events[0].environment, "m.room.message"); expect(timeline.events[0].getBody(), "Testcase"); expect(timeline.events[0].time > timeline.events[1].time, true); }); @@ -103,7 +102,7 @@ void main() { expect(updateCount, 4); expect(insertList, [0, 0, 0]); expect(insertList.length, timeline.events.length); - expect(timeline.events[0].id, "42"); + expect(timeline.events[0].eventId, "42"); expect(timeline.events[0].status, 1); client.connection.onEvent.add(EventUpdate( @@ -125,7 +124,7 @@ void main() { expect(updateCount, 5); expect(insertList, [0, 0, 0]); expect(insertList.length, timeline.events.length); - expect(timeline.events[0].id, "42"); + expect(timeline.events[0].eventId, "42"); expect(timeline.events[0].status, 2); }); @@ -189,9 +188,9 @@ void main() { expect(updateCount, 19); expect(timeline.events.length, 9); - expect(timeline.events[6].id, "1143273582443PhrSn:example.org"); - expect(timeline.events[7].id, "2143273582443PhrSn:example.org"); - expect(timeline.events[8].id, "3143273582443PhrSn:example.org"); + expect(timeline.events[6].eventId, "1143273582443PhrSn:example.org"); + expect(timeline.events[7].eventId, "2143273582443PhrSn:example.org"); + expect(timeline.events[8].eventId, "3143273582443PhrSn:example.org"); expect(room.prev_batch, "t47409-4357353_219380_26003_2265"); }); }); diff --git a/test/User_test.dart b/test/User_test.dart index dcd8b594..4d5d6416 100644 --- a/test/User_test.dart +++ b/test/User_test.dart @@ -21,6 +21,7 @@ * along with famedlysdk. If not, see . */ +import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/User.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -42,20 +43,19 @@ void main() { "power_level": powerLevel, }; - User user = User.fromJson(jsonObj, null); + User user = State.fromJson(jsonObj, null).asUser; expect(user.id, id); expect(user.membership, membership); expect(user.displayName, displayName); expect(user.avatarUrl.mxc, avatarUrl); - expect(user.powerLevel, powerLevel); expect(user.calcDisplayname(), displayName); }); test("calcDisplayname", () async { - final User user1 = User("@alice:example.com"); - final User user2 = User("@alice:example.com", displayName: "SuperAlice"); - final User user3 = User("@alice:example.com", displayName: ""); + final User user1 = User(senderId: "@alice:example.com"); + final User user2 = User(senderId: "@alice:example.com"); + final User user3 = User(senderId: "@alice:example.com"); expect(user1.calcDisplayname(), "alice"); expect(user2.calcDisplayname(), "SuperAlice"); expect(user3.calcDisplayname(), "alice"); From 1617a8b7d581163430d749ce863ebe5ead1c5c1f Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 11:41:42 +0200 Subject: [PATCH 20/75] [Tests] Write much more tests --- lib/src/Room.dart | 41 +++++--- lib/src/Store.dart | 7 +- lib/src/User.dart | 8 +- test/FakeMatrixApi.dart | 32 +++++++ test/Room_test.dart | 202 ++++++++++++++++++++++++++++------------ 5 files changed, 208 insertions(+), 82 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index ee376147..630aa491 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -73,8 +73,12 @@ class Room { return canonicalAlias.substring(1, canonicalAlias.length).split(":")[0]; if (mHeroes.length > 0) { String displayname = ""; - for (int i = 0; i < mHeroes.length; i++) - displayname += User(senderId: mHeroes[i]).calcDisplayname() + ", "; + for (int i = 0; i < mHeroes.length; i++) { + User hero = states[mHeroes[i]] != null + ? states[mHeroes[i]].asUser + : User(stateKey: mHeroes[i]); + displayname += hero.calcDisplayname() + ", "; + } return displayname.substring(0, displayname.length - 2); } return "Empty chat"; @@ -88,9 +92,9 @@ class Room { /// The avatar of the room if set by a participant. MxContent get avatar { if (states["m.room.avatar"] != null) - return MxContent(states["m.room.avatar"].content["avatar_url"]); + return MxContent(states["m.room.avatar"].content["url"]); if (mHeroes.length == 1 && states[mHeroes[0]] != null) - return (states[mHeroes[0]] as User).avatarUrl; + return states[mHeroes[0]].asUser.avatarUrl; return MxContent(""); } @@ -307,10 +311,10 @@ class Room { return res; } - /// Call the Matrix API to unban a banned user from this room. + /// Set the power level of the user with the [userID] to the value [power]. Future setPower(String userID, int power) async { + if (states["m.room.power_levels"] == null) return null; Map powerMap = states["m.room.power_levels"].content["users"]; - if (powerMap == null) return null; powerMap[userID] = power; dynamic res = await client.connection.jsonRequest( @@ -378,7 +382,7 @@ class Room { /// Sets this room as a direct chat for this user. Future addToDirectChat(String userID) async { - Map> directChats = client.directChats; + Map directChats = client.directChats; if (directChats.containsKey(userID)) if (!directChats[userID].contains(id)) directChats[userID].add(id); else @@ -413,6 +417,8 @@ class Room { Future>> roomAccountData}) async { Room newRoom = Room( id: row["id"], + membership: Membership.values + .firstWhere((e) => e.toString() == 'Membership.' + row["membership"]), notificationCount: row["notification_count"], highlightCount: row["highlight_count"], notificationSettings: row["notification_settings"], @@ -421,6 +427,8 @@ class Room { mJoinedMemberCount: row["joined_member_count"], mHeroes: row["heroes"]?.split(",") ?? [], client: matrix, + states: {}, + roomAccountData: {}, ); Map newStates = {}; @@ -463,10 +471,12 @@ class Room { /// Load all events for a given room from the store. This includes all /// senders of those events, who will be added to the participants list. Future> loadEvents() async { - return await client.store.getEventList(this); + if (client.store != null) return await client.store.getEventList(this); + return []; } /// Load all participants for a given room from the store. + @deprecated Future> loadParticipants() async { return await client.store.loadParticipants(this); } @@ -490,14 +500,14 @@ class Room { } Future getUserByMXID(String mxID) async { - if (states[mxID] != null) return states[mxID] as User; + if (states[mxID] != null) return states[mxID].asUser; final dynamic resp = await client.connection.jsonRequest( type: HTTPType.GET, action: "/client/r0/rooms/$id/state/m.room.member/$mxID"); if (resp is ErrorResponse) return null; // Somehow we miss the mxid in the response and only get the content of the event. resp["matrix_id"] = mxID; - return State.fromJson(resp, this) as User; + return State.fromJson(resp, this).asUser; } /// Searches for the event in the store. If it isn't found, try to request it @@ -514,18 +524,21 @@ class Room { } /// Returns the user's own power level. - int get ownPowerLevel { + int getPowerLevelByUserId(String userId) { int powerLevel = 0; State powerLevelState = states["m.room.power_levels"]; if (powerLevelState == null) return powerLevel; if (powerLevelState.content["users_default"] is int) powerLevel = powerLevelState.content["users_default"]; - if (powerLevelState.content["users"] is Map && - powerLevelState.content["users"][client.userID] != null) - powerLevel = powerLevelState.content["users"][client.userID]; + if (powerLevelState.content["users"] is Map && + powerLevelState.content["users"][userId] != null) + powerLevel = powerLevelState.content["users"][userId]; return powerLevel; } + /// Returns the user's own power level. + int get ownPowerLevel => getPowerLevelByUserId(client.userID); + /// Returns the power levels from all users for this room or null if not given. Map get powerLevels { State powerLevelState = states["m.room.power_levels"]; diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 5a2bb9b6..870e0f0d 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -300,7 +300,7 @@ class Store { "SELECT * FROM States WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; - return State.fromJson(res[0], room) as User; + return State.fromJson(res[0], room).asUser; } /// Loads all Users in the database to provide a contact list @@ -311,8 +311,7 @@ class Store { [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) - userList - .add(State.fromJson(res[i], Room(id: "", client: client)) as User); + userList.add(State.fromJson(res[i], Room(id: "", client: client)).asUser); return userList; } @@ -328,7 +327,7 @@ class Store { List participants = []; for (num i = 0; i < res.length; i++) { - participants.add(State.fromJson(res[i], room) as User); + participants.add(State.fromJson(res[i], room).asUser); } return participants; diff --git a/lib/src/User.dart b/lib/src/User.dart index 835a982c..e664bd0a 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -60,7 +60,7 @@ class User extends State { String get id => stateKey; /// The displayname of the user if the user has set one. - String get displayName => content["displayname"]; + String get displayName => content != null ? content["displayname"] : null; /// The membership status of the user. One of: /// join @@ -75,12 +75,14 @@ class User extends State { }); /// The avatar if the user has one. - MxContent avatarUrl; + MxContent get avatarUrl => content != null && content["avatar_url"] is String + ? MxContent(content["avatar_url"]) + : MxContent(""); /// Returns the displayname or the local part of the Matrix ID if the user /// has no displayname. String calcDisplayname() => (displayName == null || displayName.isEmpty) - ? id.replaceFirst("@", "").split(":")[0] + ? stateKey.replaceFirst("@", "").split(":")[0] : displayName; /// Call the Matrix API to kick this user from this room. diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index f63f445a..98a2ee50 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -64,6 +64,20 @@ class FakeMatrixApi extends MockClient { static final Map> api = { "GET": { + "/client/r0/rooms/!localpart:server.abc/state/m.room.member/@getme:example.com": + (var req) => { + "content": { + "membership": "join", + "displayname": "You got me", + }, + "type": "m.room.member", + "event_id": "143273582443PhrSn:example.org", + "room_id": "!localpart:server.abc", + "sender": "@getme:example.com", + "state_key": "@getme:example.com", + "origin_server_ts": 1432735824653, + "unsigned": {"age": 1234} + }, "/client/r0/rooms/!localpart:server.abc/event/1234": (var req) => { "content": { "body": "This is an example text message", @@ -503,12 +517,30 @@ class FakeMatrixApi extends MockClient { "room_id": "!1234:fakeServer.notExisting", }, "/client/r0/rooms/!localpart:server.abc/read_markers": (var reqI) => {}, + "/client/r0/rooms/!localpart:server.abc/kick": (var reqI) => {}, + "/client/r0/rooms/!localpart:server.abc/ban": (var reqI) => {}, + "/client/r0/rooms/!localpart:server.abc/unban": (var reqI) => {}, + "/client/r0/rooms/!localpart:server.abc/invite": (var reqI) => {}, }, "PUT": { "/client/r0/rooms/!1234:example.com/send/m.room.message/1234": (var reqI) => { "event_id": "42", }, + "/client/r0/rooms/!localpart:server.abc/state/m.room.name": (var reqI) => + { + "event_id": "42", + }, + "/client/r0/rooms/!localpart:server.abc/state/m.room.topic": (var reqI) => + { + "event_id": "42", + }, + "/client/r0/rooms/!localpart:server.abc/state/m.room.power_levels": + (var reqI) => { + "event_id": "42", + }, + "/client/r0/user/@test:fakeServer.notExisting/account_data/m.direct": + (var reqI) => {}, }, "DELETE": { "/unknown/token": (var req) => {"errcode": "M_UNKNOWN_TOKEN"}, diff --git a/test/Room_test.dart b/test/Room_test.dart index 897d1192..066b575d 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -24,7 +24,10 @@ import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; import 'package:famedlysdk/src/Room.dart'; +import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/Timeline.dart'; import 'package:famedlysdk/src/User.dart'; +import 'package:famedlysdk/src/utils/ChatTime.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -50,24 +53,9 @@ void main() { test("Create from json", () async { final String id = "!localpart:server.abc"; - final String name = "My Room"; final Membership membership = Membership.join; - final String topic = "This is my own room"; - final int unread = DateTime.now().millisecondsSinceEpoch; final int notificationCount = 2; final int highlightCount = 1; - final String fullyRead = "fjh82jdjifd:server.abc"; - final String notificationSettings = "all"; - final String guestAccess = "forbidden"; - final String canonicalAlias = "#testroom:example.com"; - final String historyVisibility = "invite"; - final String joinRules = "invite"; - final int now = DateTime.now().millisecondsSinceEpoch; - final String msgtype = "m.text"; - final String body = "Hello World"; - final String formatted_body = "Hello World"; - final String contentJson = - '{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body"}'; final List heroes = [ "@alice:matrix.org", "@bob:example.com", @@ -77,35 +65,10 @@ void main() { Map jsonObj = { "id": id, "membership": membership.toString().split('.').last, - "topic": name, - "description": topic, "avatar_url": "", "notification_count": notificationCount, "highlight_count": highlightCount, - "unread": unread, - "fully_read": fullyRead, - "notification_settings": notificationSettings, - "direct_chat_matrix_id": "", - "draft": "", "prev_batch": "", - "guest_access": guestAccess, - "history_visibility": historyVisibility, - "join_rules": joinRules, - "canonical_alias": canonicalAlias, - "power_events_default": 0, - "power_state_default": 0, - "power_redact": 0, - "power_invite": 0, - "power_ban": 0, - "power_kick": 0, - "power_user_default": 0, - "power_event_avatar": 0, - "power_event_history_visibility": 0, - "power_event_canonical_alias": 0, - "power_event_aliases": 0, - "power_event_name": 0, - "power_event_power_levels": 0, - "content_json": contentJson, "joined_member_count": notificationCount, "invited_member_count": notificationCount, "heroes": heroes.join(","), @@ -115,33 +78,69 @@ void main() { expect(room.id, id); expect(room.membership, membership); - expect(room.name, name); - expect(room.displayname, name); - expect(room.topic, topic); - expect(room.avatar.mxc, ""); expect(room.notificationCount, notificationCount); expect(room.highlightCount, highlightCount); - expect(room.unread.toTimeStamp(), unread); - expect(room.fullyRead, fullyRead); - expect(room.notificationSettings, notificationSettings); - expect(room.directChatMatrixID, ""); - expect(room.canonicalAlias, canonicalAlias); - expect(room.prev_batch, ""); - expect(room.lastMessage, body); - expect(room.timeCreated.toTimeStamp() >= now, true); - room.powerLevels.forEach((String key, int value) { - expect(value, 0); - }); expect(room.mJoinedMemberCount, notificationCount); expect(room.mInvitedMemberCount, notificationCount); expect(room.mHeroes, heroes); - - jsonObj["topic"] = ""; - room = await Room.getRoomFromTableRow(jsonObj, matrix); - expect(room.displayname, "testroom"); - jsonObj["canonical_alias"] = ""; - room = await Room.getRoomFromTableRow(jsonObj, matrix); expect(room.displayname, "alice, bob, charley"); + + room.states["m.room.canonical_alias"] = State( + senderId: "@test:example.com", + typeKey: "m.room.canonical_alias", + roomId: room.id, + room: room, + eventId: "123", + content: {"alias": "#testalias:example.com"}, + stateKey: ""); + expect(room.displayname, "testalias"); + expect(room.canonicalAlias, "#testalias:example.com"); + + room.states["m.room.name"] = State( + senderId: "@test:example.com", + typeKey: "m.room.name", + roomId: room.id, + room: room, + eventId: "123", + content: {"name": "testname"}, + stateKey: ""); + expect(room.displayname, "testname"); + + expect(room.topic, ""); + room.states["m.room.topic"] = State( + senderId: "@test:example.com", + typeKey: "m.room.topic", + roomId: room.id, + room: room, + eventId: "123", + content: {"topic": "testtopic"}, + stateKey: ""); + expect(room.topic, "testtopic"); + + expect(room.avatar.mxc, ""); + room.states["m.room.avatar"] = State( + senderId: "@test:example.com", + typeKey: "m.room.avatar", + roomId: room.id, + room: room, + eventId: "123", + content: {"url": "mxc://testurl"}, + stateKey: ""); + expect(room.avatar.mxc, "mxc://testurl"); + + expect(room.lastEvent, null); + room.states["m.room.message"] = State( + senderId: "@test:example.com", + typeKey: "m.room.message", + roomId: room.id, + room: room, + eventId: "12345", + time: ChatTime.now(), + content: {"msgtype": "m.text", "body": "test"}, + stateKey: ""); + expect(room.lastEvent.eventId, "12345"); + expect(room.lastMessage, "test"); + expect(room.timeCreated, room.lastEvent.time); }); test("sendReadReceipt", () async { @@ -165,5 +164,86 @@ void main() { final Event event = await room.getEventById("1234"); expect(event.eventId, "143273582443PhrSn:example.org"); }); + + test("setName", () async { + final dynamic resp = await room.setName("Testname"); + expect(resp["event_id"], "42"); + }); + + test("setDescription", () async { + final dynamic resp = await room.setDescription("Testname"); + expect(resp["event_id"], "42"); + }); + + test("kick", () async { + final dynamic resp = await room.kick("Testname"); + expect(resp, {}); + }); + + test("ban", () async { + final dynamic resp = await room.ban("Testname"); + expect(resp, {}); + }); + + test("unban", () async { + final dynamic resp = await room.unban("Testname"); + expect(resp, {}); + }); + + test("PowerLevels", () async { + room.states["m.room.power_levels"] = State( + senderId: "@test:example.com", + typeKey: "m.room.power_levels", + roomId: room.id, + room: room, + eventId: "123", + content: { + "ban": 50, + "events": {"m.room.name": 100, "m.room.power_levels": 100}, + "events_default": 0, + "invite": 50, + "kick": 50, + "notifications": {"room": 20}, + "redact": 50, + "state_default": 50, + "users": {"@test:fakeServer.notExisting": 100}, + "users_default": 10 + }, + stateKey: ""); + expect(room.ownPowerLevel, 100); + expect(room.getPowerLevelByUserId(matrix.userID), room.ownPowerLevel); + expect(room.getPowerLevelByUserId("@nouser:example.com"), 10); + expect(room.powerLevels, + room.states["m.room.power_levels"].content["users"]); + final dynamic resp = + await room.setPower("@test:fakeServer.notExisting", 90); + expect(resp["event_id"], "42"); + }); + + test("invite", () async { + final dynamic resp = await room.invite("Testname"); + expect(resp, {}); + }); + + test("addToDirectChat", () async { + final dynamic resp = await room.addToDirectChat("Testname"); + expect(resp, {}); + }); + }); + + test("getTimeline", () async { + final Timeline timeline = await room.getTimeline(); + expect(timeline.events, []); + }); + + test("loadEvents", () async { + final List events = await room.loadEvents(); + expect(events, []); + }); + + test("getUserByMXID", () async { + final User user = await room.getUserByMXID("@getme:example.com"); + expect(user.stateKey, "@getme:example.com"); + expect(user.calcDisplayname(), "You got me"); }); } From b4ae8b47f8dc73c9acb1a0cf45c67b6d6f43be75 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 11:54:39 +0200 Subject: [PATCH 21/75] [Tests] Fix user tests --- lib/src/RawEvent.dart | 2 +- lib/src/Room.dart | 4 ++-- lib/src/State.dart | 4 ++-- lib/src/User.dart | 23 ++++++++++++++++++++++- test/Client_test.dart | 4 ++-- test/Room_test.dart | 26 +++++++++++++------------- test/User_test.dart | 24 +++++++++++++++--------- 7 files changed, 57 insertions(+), 30 deletions(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 8deff6f4..c4bf7b5f 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -44,7 +44,7 @@ class RawEvent { /// The user who has sent this event if it is not a global account data event. final String senderId; - User get sender => room.states[senderId] ?? User(senderId: senderId); + User get sender => room.states[senderId] ?? User(senderId); /// The time this event has received at the server. May be null for events like /// account data. diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 630aa491..07b0f0db 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -76,7 +76,7 @@ class Room { for (int i = 0; i < mHeroes.length; i++) { User hero = states[mHeroes[i]] != null ? states[mHeroes[i]].asUser - : User(stateKey: mHeroes[i]); + : User(mHeroes[i]); displayname += hero.calcDisplayname() + ", "; } return displayname.substring(0, displayname.length - 2); @@ -155,7 +155,7 @@ class Room { if (mHeroes.length > 0) { String displayname = ""; for (int i = 0; i < mHeroes.length; i++) - displayname += User(senderId: mHeroes[i]).calcDisplayname() + ", "; + displayname += User(mHeroes[i]).calcDisplayname() + ", "; return displayname.substring(0, displayname.length - 2); } return "Empty chat"; diff --git a/lib/src/State.dart b/lib/src/State.dart index 52dee1f0..bf083fca 100644 --- a/lib/src/State.dart +++ b/lib/src/State.dart @@ -36,7 +36,7 @@ class State extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; - User get stateKeyUser => room.states[stateKey] ?? User(senderId: stateKey); + User get stateKeyUser => room.states[stateKey] ?? User(stateKey); State( {this.prevContent, @@ -96,7 +96,7 @@ class State extends RawEvent { /// stateKey. Otherwise it will be the [type] as a string. String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey; - User get asUser => User( + User get asUser => User.fromState( stateKey: stateKey, prevContent: prevContent, content: content, diff --git a/lib/src/User.dart b/lib/src/User.dart index e664bd0a..11b5206d 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -33,7 +33,28 @@ enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. class User extends State { - User( + factory User( + String id, { + String membership, + String displayName, + String avatarUrl, + Room room, + }) { + Map content = {}; + if (membership != null) content["membership"] = membership; + if (displayName != null) content["displayname"] = displayName; + if (avatarUrl != null) content["avatar_url"] = avatarUrl; + return User.fromState( + stateKey: id, + content: content, + typeKey: "m.room.member", + roomId: room?.id, + room: room, + time: ChatTime.now(), + ); + } + + User.fromState( {dynamic prevContent, String stateKey, dynamic content, diff --git a/test/Client_test.dart b/test/Client_test.dart index 855da000..8239e0b0 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -263,8 +263,8 @@ void main() { test('createGroup', () async { final List users = [ - User(senderId: "@alice:fakeServer.notExisting"), - User(senderId: "@bob:fakeServer.notExisting") + User("@alice:fakeServer.notExisting"), + User("@bob:fakeServer.notExisting") ]; final String newID = await matrix.createGroup(users); expect(newID, "!1234:fakeServer.notExisting"); diff --git a/test/Room_test.dart b/test/Room_test.dart index 066b575d..0b251a1f 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -229,21 +229,21 @@ void main() { final dynamic resp = await room.addToDirectChat("Testname"); expect(resp, {}); }); - }); - test("getTimeline", () async { - final Timeline timeline = await room.getTimeline(); - expect(timeline.events, []); - }); + test("getTimeline", () async { + final Timeline timeline = await room.getTimeline(); + expect(timeline.events, []); + }); - test("loadEvents", () async { - final List events = await room.loadEvents(); - expect(events, []); - }); + test("loadEvents", () async { + final List events = await room.loadEvents(); + expect(events, []); + }); - test("getUserByMXID", () async { - final User user = await room.getUserByMXID("@getme:example.com"); - expect(user.stateKey, "@getme:example.com"); - expect(user.calcDisplayname(), "You got me"); + test("getUserByMXID", () async { + final User user = await room.getUserByMXID("@getme:example.com"); + expect(user.stateKey, "@getme:example.com"); + expect(user.calcDisplayname(), "You got me"); + }); }); } diff --git a/test/User_test.dart b/test/User_test.dart index 4d5d6416..7e192c2a 100644 --- a/test/User_test.dart +++ b/test/User_test.dart @@ -33,14 +33,20 @@ void main() { final Membership membership = Membership.join; final String displayName = "Alice"; final String avatarUrl = ""; - final int powerLevel = 50; final Map jsonObj = { - "matrix_id": id, - "displayname": displayName, - "avatar_url": avatarUrl, - "membership": membership.toString().split('.').last, - "power_level": powerLevel, + "content": { + "membership": "join", + "avatar_url": avatarUrl, + "displayname": displayName + }, + "type": "m.room.member", + "event_id": "143273582443PhrSn:example.org", + "room_id": "!636q39766251:example.com", + "sender": id, + "origin_server_ts": 1432735824653, + "unsigned": {"age": 1234}, + "state_key": id }; User user = State.fromJson(jsonObj, null).asUser; @@ -53,9 +59,9 @@ void main() { }); test("calcDisplayname", () async { - final User user1 = User(senderId: "@alice:example.com"); - final User user2 = User(senderId: "@alice:example.com"); - final User user3 = User(senderId: "@alice:example.com"); + final User user1 = User("@alice:example.com"); + final User user2 = User("@SuperAlice:example.com"); + final User user3 = User("@alice:example.com"); expect(user1.calcDisplayname(), "alice"); expect(user2.calcDisplayname(), "SuperAlice"); expect(user3.calcDisplayname(), "alice"); From 3236224496cb78c6d1bf76932af89880f6212aff Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 11:57:40 +0200 Subject: [PATCH 22/75] [Eventtests] Fix event tests --- test/Event_test.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/Event_test.dart b/test/Event_test.dart index 1e501b6c..82091161 100644 --- a/test/Event_test.dart +++ b/test/Event_test.dart @@ -25,6 +25,7 @@ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/RawEvent.dart'; +import 'package:famedlysdk/src/State.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -62,6 +63,12 @@ void main() { expect(event.formattedText, formatted_body); expect(event.getBody(), body); expect(event.type, EventTypes.Text); + jsonObj["state_key"] = ""; + State state = State.fromJson(jsonObj, null); + expect(state.eventId, id); + expect(state.stateKey, ""); + expect(state.key, "m.room.message"); + expect(state.timelineEvent.status, 1); }); test("Test all EventTypes", () async { Event event; From 1e46af6b67d19b3da651ab789df5333efa747f9b Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 12:00:24 +0200 Subject: [PATCH 23/75] [Room] Minor bugfix --- test/Timeline_test.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/Timeline_test.dart b/test/Timeline_test.dart index 3b37c6e7..956f1525 100644 --- a/test/Timeline_test.dart +++ b/test/Timeline_test.dart @@ -41,7 +41,12 @@ void main() { client.connection.httpClient = FakeMatrixApi(); client.homeserver = "https://fakeServer.notExisting"; - Room room = Room(id: roomID, client: client, prev_batch: "1234"); + Room room = Room( + id: roomID, + client: client, + prev_batch: "1234", + states: {}, + roomAccountData: {}); Timeline timeline = Timeline( room: room, events: [], From 35b9e0db401f968a4f2b660a4ebec12530ec4f8d Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 12:29:09 +0200 Subject: [PATCH 24/75] [Event] Make Event subclass of State --- lib/src/Event.dart | 11 ++++++++++- lib/src/Store.dart | 25 +++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/src/Event.dart b/lib/src/Event.dart index eba53c80..8f0feb9d 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -21,6 +21,7 @@ * along with famedlysdk. If not, see . */ +import 'package:famedlysdk/src/State.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; @@ -28,7 +29,7 @@ import './Room.dart'; import './RawEvent.dart'; /// Defines a timeline event for a room. -class Event extends RawEvent { +class Event extends State { /// The status of this event. /// -1=ERROR /// 0=SENDING @@ -45,6 +46,8 @@ class Event extends RawEvent { String senderId, ChatTime time, dynamic unsigned, + dynamic prevContent, + String stateKey, Room room}) : super( content: content, @@ -54,6 +57,8 @@ class Event extends RawEvent { senderId: senderId, time: time, unsigned: unsigned, + prevContent: prevContent, + stateKey: stateKey, room: room); /// Get a State event from a table row or from the event stream. @@ -62,6 +67,8 @@ class Event extends RawEvent { RawEvent.getMapFromPayload(jsonPayload['content']); final Map unsigned = RawEvent.getMapFromPayload(jsonPayload['unsigned']); + final Map prevContent = + RawEvent.getMapFromPayload(jsonPayload['prev_content']); return Event( status: jsonPayload['status'] ?? 1, content: content, @@ -71,6 +78,8 @@ class Event extends RawEvent { senderId: jsonPayload['sender'], time: ChatTime(jsonPayload['origin_server_ts']), unsigned: unsigned, + prevContent: prevContent, + stateKey: jsonPayload['state_key'], room: room); } diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 870e0f0d..592bafa5 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -250,16 +250,19 @@ class Store { ]); else txn.rawInsert( - "INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?)", [ - eventContent["event_id"], - chat_id, - eventContent["origin_server_ts"], - eventContent["sender"], - eventContent["type"], - json.encode(eventContent["unsigned"] ?? ""), - json.encode(eventContent["content"]), - status - ]); + "INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [ + eventContent["event_id"], + chat_id, + eventContent["origin_server_ts"], + eventContent["sender"], + eventContent["type"], + json.encode(eventContent["unsigned"] ?? ""), + json.encode(eventContent["content"]), + json.encode(eventContent["prevContent"]), + eventContent["state_key"], + status + ]); // Is there a transaction id? Then delete the event with this id. if (status != -1 && @@ -474,6 +477,8 @@ class Store { 'type TEXT, ' + 'unsigned TEXT, ' + 'content TEXT, ' + + 'prev_content TEXT, ' + + 'state_key TEXT, ' + "status INTEGER, " + 'UNIQUE(id))', From fbbb90aac64be32f3531d90b07b176f8933c30dd Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 12:36:37 +0200 Subject: [PATCH 25/75] [SDK] Update file list --- lib/famedlysdk.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index 0636a0b3..ceda8343 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -33,8 +33,10 @@ export 'package:famedlysdk/src/utils/MxContent.dart'; export 'package:famedlysdk/src/Client.dart'; export 'package:famedlysdk/src/Connection.dart'; export 'package:famedlysdk/src/Event.dart'; +export 'package:famedlysdk/src/RawEvent.dart'; export 'package:famedlysdk/src/Room.dart'; export 'package:famedlysdk/src/RoomList.dart'; +export 'package:famedlysdk/src/State.dart'; export 'package:famedlysdk/src/Store.dart'; export 'package:famedlysdk/src/Timeline.dart'; export 'package:famedlysdk/src/User.dart'; From 8d563c97571e178057b217b72434d3c4d554de3f Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 12:51:07 +0200 Subject: [PATCH 26/75] [RoomState] Rename State to RoomState --- lib/famedlysdk.dart | 2 +- lib/src/Event.dart | 4 ++-- lib/src/Room.dart | 16 ++++++++-------- lib/src/RoomList.dart | 4 ++-- lib/src/{State.dart => RoomState.dart} | 8 ++++---- lib/src/Store.dart | 9 +++++---- lib/src/User.dart | 4 ++-- test/Event_test.dart | 4 ++-- test/Room_test.dart | 14 +++++++------- test/User_test.dart | 4 ++-- 10 files changed, 35 insertions(+), 34 deletions(-) rename lib/src/{State.dart => RoomState.dart} (95%) diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index ceda8343..1be53247 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -36,7 +36,7 @@ export 'package:famedlysdk/src/Event.dart'; export 'package:famedlysdk/src/RawEvent.dart'; export 'package:famedlysdk/src/Room.dart'; export 'package:famedlysdk/src/RoomList.dart'; -export 'package:famedlysdk/src/State.dart'; +export 'package:famedlysdk/src/RoomState.dart'; export 'package:famedlysdk/src/Store.dart'; export 'package:famedlysdk/src/Timeline.dart'; export 'package:famedlysdk/src/User.dart'; diff --git a/lib/src/Event.dart b/lib/src/Event.dart index 8f0feb9d..52526938 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -21,7 +21,7 @@ * along with famedlysdk. If not, see . */ -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; @@ -29,7 +29,7 @@ import './Room.dart'; import './RawEvent.dart'; /// Defines a timeline event for a room. -class Event extends State { +class Event extends RoomState { /// The status of this event. /// -1=ERROR /// 0=SENDING diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 07b0f0db..f27c29d8 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -24,7 +24,7 @@ import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; import 'package:famedlysdk/src/RoomAccountData.dart'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; @@ -54,7 +54,7 @@ class Room { int mJoinedMemberCount; int mInvitedMemberCount; - Map states = {}; + Map states = {}; Map roomAccountData = {}; @@ -431,11 +431,11 @@ class Room { roomAccountData: {}, ); - Map newStates = {}; + Map newStates = {}; if (states != null) { List> rawStates = await states; for (int i = 0; i < rawStates.length; i++) { - State newState = State.fromJson(rawStates[i], newRoom); + RoomState newState = RoomState.fromJson(rawStates[i], newRoom); newStates[newState.key] = newState; } newRoom.states = newStates; @@ -492,7 +492,7 @@ class Room { return participants; for (num i = 0; i < res["chunk"].length; i++) { - User newUser = State.fromJson(res["chunk"][i], this).asUser; + User newUser = RoomState.fromJson(res["chunk"][i], this).asUser; if (newUser.membership != Membership.leave) participants.add(newUser); } @@ -507,7 +507,7 @@ class Room { if (resp is ErrorResponse) return null; // Somehow we miss the mxid in the response and only get the content of the event. resp["matrix_id"] = mxID; - return State.fromJson(resp, this).asUser; + return RoomState.fromJson(resp, this).asUser; } /// Searches for the event in the store. If it isn't found, try to request it @@ -526,7 +526,7 @@ class Room { /// Returns the user's own power level. int getPowerLevelByUserId(String userId) { int powerLevel = 0; - State powerLevelState = states["m.room.power_levels"]; + RoomState powerLevelState = states["m.room.power_levels"]; if (powerLevelState == null) return powerLevel; if (powerLevelState.content["users_default"] is int) powerLevel = powerLevelState.content["users_default"]; @@ -541,7 +541,7 @@ class Room { /// Returns the power levels from all users for this room or null if not given. Map get powerLevels { - State powerLevelState = states["m.room.power_levels"]; + RoomState powerLevelState = states["m.room.power_levels"]; if (powerLevelState.content["users"] is Map) return powerLevelState.content["users"]; return null; diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 6096dd44..d24a4247 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -24,7 +24,7 @@ import 'dart:async'; import 'dart:core'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'Client.dart'; import 'Room.dart'; @@ -141,7 +141,7 @@ class RoomList { final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID); if (!found) return; - State stateEvent = State.fromJson(eventUpdate.content, rooms[j]); + RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]); if (rooms[j].states[stateEvent.key] != null && rooms[j].states[stateEvent.key].time > stateEvent.time) return; rooms[j].states[stateEvent.key] = stateEvent; diff --git a/lib/src/State.dart b/lib/src/RoomState.dart similarity index 95% rename from lib/src/State.dart rename to lib/src/RoomState.dart index bf083fca..3e13e37e 100644 --- a/lib/src/State.dart +++ b/lib/src/RoomState.dart @@ -26,7 +26,7 @@ import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; import './RawEvent.dart'; -class State extends RawEvent { +class RoomState extends RawEvent { /// Optional. The previous content for this state. /// This will be present only for state events appearing in the timeline. /// If this is not a state event, or there is no previous content, this key will be null. @@ -38,7 +38,7 @@ class State extends RawEvent { User get stateKeyUser => room.states[stateKey] ?? User(stateKey); - State( + RoomState( {this.prevContent, this.stateKey, dynamic content, @@ -60,14 +60,14 @@ class State extends RawEvent { room: room); /// Get a State event from a table row or from the event stream. - factory State.fromJson(Map jsonPayload, Room room) { + factory RoomState.fromJson(Map jsonPayload, Room room) { final Map content = RawEvent.getMapFromPayload(jsonPayload['content']); final Map unsigned = RawEvent.getMapFromPayload(jsonPayload['unsigned']); final Map prevContent = RawEvent.getMapFromPayload(jsonPayload['prev_content']); - return State( + return RoomState( stateKey: jsonPayload['state_key'], prevContent: prevContent, content: content, diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 592bafa5..a2520f85 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -27,7 +27,7 @@ import 'dart:core'; import 'package:famedlysdk/src/AccountData.dart'; import 'package:famedlysdk/src/Presence.dart'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:path/path.dart' as p; import 'package:sqflite/sqflite.dart'; @@ -303,7 +303,7 @@ class Store { "SELECT * FROM States WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; - return State.fromJson(res[0], room).asUser; + return RoomState.fromJson(res[0], room).asUser; } /// Loads all Users in the database to provide a contact list @@ -314,7 +314,8 @@ class Store { [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) - userList.add(State.fromJson(res[i], Room(id: "", client: client)).asUser); + userList + .add(RoomState.fromJson(res[i], Room(id: "", client: client)).asUser); return userList; } @@ -330,7 +331,7 @@ class Store { List participants = []; for (num i = 0; i < res.length; i++) { - participants.add(State.fromJson(res[i], room).asUser); + participants.add(RoomState.fromJson(res[i], room).asUser); } return participants; diff --git a/lib/src/User.dart b/lib/src/User.dart index 11b5206d..311c5fe0 100644 --- a/lib/src/User.dart +++ b/lib/src/User.dart @@ -22,7 +22,7 @@ */ import 'package:famedlysdk/src/Room.dart'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:famedlysdk/src/responses/ErrorResponse.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import 'package:famedlysdk/src/utils/MxContent.dart'; @@ -32,7 +32,7 @@ import 'Connection.dart'; enum Membership { join, invite, leave, ban } /// Represents a Matrix User which may be a participant in a Matrix Room. -class User extends State { +class User extends RoomState { factory User( String id, { String membership, diff --git a/test/Event_test.dart b/test/Event_test.dart index 82091161..980b589e 100644 --- a/test/Event_test.dart +++ b/test/Event_test.dart @@ -25,7 +25,7 @@ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/RawEvent.dart'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -64,7 +64,7 @@ void main() { expect(event.getBody(), body); expect(event.type, EventTypes.Text); jsonObj["state_key"] = ""; - State state = State.fromJson(jsonObj, null); + RoomState state = RoomState.fromJson(jsonObj, null); expect(state.eventId, id); expect(state.stateKey, ""); expect(state.key, "m.room.message"); diff --git a/test/Room_test.dart b/test/Room_test.dart index 0b251a1f..14c2fc95 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -24,7 +24,7 @@ import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; import 'package:famedlysdk/src/Room.dart'; -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:famedlysdk/src/Timeline.dart'; import 'package:famedlysdk/src/User.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; @@ -85,7 +85,7 @@ void main() { expect(room.mHeroes, heroes); expect(room.displayname, "alice, bob, charley"); - room.states["m.room.canonical_alias"] = State( + room.states["m.room.canonical_alias"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.canonical_alias", roomId: room.id, @@ -96,7 +96,7 @@ void main() { expect(room.displayname, "testalias"); expect(room.canonicalAlias, "#testalias:example.com"); - room.states["m.room.name"] = State( + room.states["m.room.name"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.name", roomId: room.id, @@ -107,7 +107,7 @@ void main() { expect(room.displayname, "testname"); expect(room.topic, ""); - room.states["m.room.topic"] = State( + room.states["m.room.topic"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.topic", roomId: room.id, @@ -118,7 +118,7 @@ void main() { expect(room.topic, "testtopic"); expect(room.avatar.mxc, ""); - room.states["m.room.avatar"] = State( + room.states["m.room.avatar"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.avatar", roomId: room.id, @@ -129,7 +129,7 @@ void main() { expect(room.avatar.mxc, "mxc://testurl"); expect(room.lastEvent, null); - room.states["m.room.message"] = State( + room.states["m.room.message"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.message", roomId: room.id, @@ -191,7 +191,7 @@ void main() { }); test("PowerLevels", () async { - room.states["m.room.power_levels"] = State( + room.states["m.room.power_levels"] = RoomState( senderId: "@test:example.com", typeKey: "m.room.power_levels", roomId: room.id, diff --git a/test/User_test.dart b/test/User_test.dart index 7e192c2a..dcb6c224 100644 --- a/test/User_test.dart +++ b/test/User_test.dart @@ -21,7 +21,7 @@ * along with famedlysdk. If not, see . */ -import 'package:famedlysdk/src/State.dart'; +import 'package:famedlysdk/src/RoomState.dart'; import 'package:famedlysdk/src/User.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -49,7 +49,7 @@ void main() { "state_key": id }; - User user = State.fromJson(jsonObj, null).asUser; + User user = RoomState.fromJson(jsonObj, null).asUser; expect(user.id, id); expect(user.membership, membership); From e1470b0f97e2d186e1a0eb0a794dada474d1494a Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:00:56 +0200 Subject: [PATCH 27/75] [Room] Set default room attr --- lib/src/Room.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index f27c29d8..7d7b1da5 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -131,17 +131,17 @@ class Room { Room({ this.id, - this.membership, - this.notificationCount, - this.highlightCount, + this.membership = Membership.join, + this.notificationCount = 0, + this.highlightCount = 0, this.prev_batch = "", this.client, this.notificationSettings, - this.mHeroes, - this.mInvitedMemberCount, - this.mJoinedMemberCount, - this.states, - this.roomAccountData, + this.mHeroes = const [], + this.mInvitedMemberCount = 0, + this.mJoinedMemberCount = 0, + this.states = const {}, + this.roomAccountData = const {}, }); /// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and From eb68a418c0a8cf8687b6c984f3d4ccca2187926a Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:05:23 +0200 Subject: [PATCH 28/75] [Event] Set default status --- lib/src/Event.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Event.dart b/lib/src/Event.dart index 52526938..02580b57 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -38,7 +38,7 @@ class Event extends RoomState { int status; Event( - {this.status, + {this.status = 2, dynamic content, String typeKey, String eventId, From f2bbe978a87fb162924f5c4870fd8bb0b1b06397 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:31:04 +0200 Subject: [PATCH 29/75] [Store] Fix query bug --- lib/src/Store.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index a2520f85..d3f8a06c 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -340,11 +340,7 @@ class Store { /// Returns a list of events for the given room and sets all participants. Future> getEventList(Room room) async { List> eventRes = await db.rawQuery( - "SELECT * " + - " FROM Events " + - " WHERE room_id=?" + - " GROUP BY id " + - " ORDER BY origin_server_ts DESC", + "SELECT * " + " FROM Events " + " WHERE room_id=?" + " GROUP BY id", [room.id]); List eventList = []; From 5abd5065fa0a5ccc5a64522d63a8219dd355bd78 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:31:43 +0200 Subject: [PATCH 30/75] [Store] Fix another query bug --- lib/src/Store.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index d3f8a06c..66d2c737 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -340,7 +340,11 @@ class Store { /// Returns a list of events for the given room and sets all participants. Future> getEventList(Room room) async { List> eventRes = await db.rawQuery( - "SELECT * " + " FROM Events " + " WHERE room_id=?" + " GROUP BY id", + "SELECT * " + + " FROM Events " + + " WHERE room_id=?" + + " GROUP BY id " + + " ORDER BY origin_server_ts DESC", [room.id]); List eventList = []; @@ -362,8 +366,7 @@ class Store { " WHERE rooms.membership" + (onlyLeft ? "=" : "!=") + "'leave' " + - " GROUP BY rooms.id " + - " ORDER BY origin_server_ts DESC "); + " GROUP BY rooms.id "); List roomList = []; for (num i = 0; i < res.length; i++) { Room room = await Room.getRoomFromTableRow(res[i], client, From cc84551f4d8f22a01be4f90a9efc137275def3bd Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:53:31 +0200 Subject: [PATCH 31/75] [Store] Update database version --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 66d2c737..7a957f31 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -58,7 +58,7 @@ class Store { _init() async { var databasePath = await getDatabasesPath(); String path = p.join(databasePath, "FluffyMatrix.db"); - _db = await openDatabase(path, version: 11, + _db = await openDatabase(path, version: 12, onCreate: (Database db, int version) async { await createTables(db); }, onUpgrade: (Database db, int oldVersion, int newVersion) async { From 357a633d0ef07eb8cfea91d06c9858879bae6060 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 13:57:50 +0200 Subject: [PATCH 32/75] [Store] Fix scheme --- lib/src/Store.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 7a957f31..a6ba46ab 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -466,7 +466,7 @@ class Store { 'joined_member_count INTEGER, ' + 'invited_member_count INTEGER, ' + 'heroes TEXT, ' + - 'UNIQUE(id))', + 'UNIQUE(room_id))', /// The database scheme for the TimelineEvent class. 'Events': 'CREATE TABLE IF NOT EXISTS Events(' + @@ -480,7 +480,7 @@ class Store { 'prev_content TEXT, ' + 'state_key TEXT, ' + "status INTEGER, " + - 'UNIQUE(id))', + 'UNIQUE(event_id))', /// The database scheme for room states. 'State': 'CREATE TABLE IF NOT EXISTS State(' + From 3d28b71250536863f6b274be233b75d5a67a54d1 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:02:54 +0200 Subject: [PATCH 33/75] [Store] Fix getRoomList --- lib/src/Store.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index a6ba46ab..83828f64 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -363,10 +363,10 @@ class Store { if (onlyDirect && onlyGroups) return []; List> res = await db.rawQuery("SELECT * " + " FROM Rooms" + - " WHERE rooms.membership" + + " WHERE membership" + (onlyLeft ? "=" : "!=") + "'leave' " + - " GROUP BY rooms.id "); + " GROUP BY room_id "); List roomList = []; for (num i = 0; i < res.length; i++) { Room room = await Room.getRoomFromTableRow(res[i], client, From 4a0822799dcf37c8c06aaae7a8ffa4662c6efc27 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:08:15 +0200 Subject: [PATCH 34/75] [Store] Fix table name --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 83828f64..4168643c 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -509,7 +509,7 @@ class Store { 'UNIQUE(type,room_id))', /// The database scheme for room states. - 'Presence': 'CREATE TABLE IF NOT EXISTS Presence(' + + 'Presences': 'CREATE TABLE IF NOT EXISTS Presences(' + 'type TEXT PRIMARY KEY, ' + 'sender TEXT, ' + 'content TEXT, ' + From c2c93c2da9ee808b9c97bb4453c91ab7b7ca51c9 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:09:11 +0200 Subject: [PATCH 35/75] [Store] Bump version --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 4168643c..3afdc2b0 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -58,7 +58,7 @@ class Store { _init() async { var databasePath = await getDatabasesPath(); String path = p.join(databasePath, "FluffyMatrix.db"); - _db = await openDatabase(path, version: 12, + _db = await openDatabase(path, version: 13, onCreate: (Database db, int version) async { await createTables(db); }, onUpgrade: (Database db, int oldVersion, int newVersion) async { From d661fb428976907020eef44df700def026ef312d Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:13:32 +0200 Subject: [PATCH 36/75] [Store] Fix more queries --- lib/src/Store.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 3afdc2b0..acb55b00 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -156,8 +156,8 @@ class Store { } Future storeRoomPrevBatch(Room room) async { - await _db.rawUpdate( - "UPDATE Rooms SET prev_batch=? WHERE id=?", [room.prev_batch, room.id]); + await _db.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?", + [room.prev_batch, room.id]); return null; } @@ -189,7 +189,7 @@ class Store { updateQuery += ", heroes=?"; updateArgs.add(roomUpdate.summary.mHeroes.join(",")); } - updateQuery += " WHERE id=?"; + updateQuery += " WHERE room_id=?"; updateArgs.add(roomUpdate.id); txn.rawUpdate(updateQuery, updateArgs); @@ -343,7 +343,7 @@ class Store { "SELECT * " + " FROM Events " + " WHERE room_id=?" + - " GROUP BY id " + + " GROUP BY event_id " + " ORDER BY origin_server_ts DESC", [room.id]); @@ -379,7 +379,7 @@ class Store { /// Returns a room without events and participants. Future getRoomById(String id) async { List> res = - await db.rawQuery("SELECT * FROM Rooms WHERE id=?", [id]); + await db.rawQuery("SELECT * FROM Rooms WHERE room_id=?", [id]); if (res.length != 1) return null; return Room.getRoomFromTableRow(res[0], client, states: getStatesFromRoomId(id)); @@ -390,7 +390,7 @@ class Store { } Future forgetRoom(String roomID) async { - await db.rawDelete("DELETE FROM Rooms WHERE id=?", [roomID]); + await db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]); return; } From 471f9f8c49e06106031fffb3c46052fc234432d7 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:17:10 +0200 Subject: [PATCH 37/75] [Store] Fix RoomStates table --- lib/src/Store.dart | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index acb55b00..e5a26cad 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -58,7 +58,7 @@ class Store { _init() async { var databasePath = await getDatabasesPath(); String path = p.join(databasePath, "FluffyMatrix.db"); - _db = await openDatabase(path, version: 13, + _db = await openDatabase(path, version: 14, onCreate: (Database db, int version) async { await createTables(db); }, onUpgrade: (Database db, int oldVersion, int newVersion) async { @@ -276,17 +276,18 @@ class Store { if (eventUpdate.content["event_id"] != null) { txn.rawInsert( - "INSERT OR REPLACE INTO State VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - eventContent["event_id"], - chat_id, - eventContent["origin_server_ts"], - eventContent["sender"], - state_key, - json.encode(eventContent["unsigned"] ?? ""), - json.encode(eventContent["prev_content"] ?? ""), - eventContent["type"], - json.encode(eventContent["content"]), - ]); + "INSERT OR REPLACE INTO RoomStates VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", + [ + eventContent["event_id"], + chat_id, + eventContent["origin_server_ts"], + eventContent["sender"], + state_key, + json.encode(eventContent["unsigned"] ?? ""), + json.encode(eventContent["prev_content"] ?? ""), + eventContent["type"], + json.encode(eventContent["content"]), + ]); } else txn.rawInsert("INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?)", [ eventContent["type"], @@ -300,7 +301,7 @@ class Store { /// Returns a User object by a given Matrix ID and a Room. Future getUser({String matrixID, Room room}) async { List> res = await db.rawQuery( - "SELECT * FROM States WHERE state_key=? AND room_id=?", + "SELECT * FROM RoomStates WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; return RoomState.fromJson(res[0], room).asUser; @@ -310,7 +311,7 @@ class Store { /// except users who are in the Room with the ID [exceptRoomID]. Future> loadContacts({String exceptRoomID = ""}) async { List> res = await db.rawQuery( - "SELECT * FROM States WHERE state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key", + "SELECT * FROM RoomStates WHERE state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key", [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) @@ -323,7 +324,7 @@ class Store { Future> loadParticipants(Room room) async { List> res = await db.rawQuery( "SELECT * " + - " FROM States " + + " FROM RoomStates " + " WHERE room_id=? " + " AND type='m.room.member'", [room.id]); @@ -386,7 +387,7 @@ class Store { } Future>> getStatesFromRoomId(String id) async { - return db.rawQuery("SELECT * FROM States WHERE room_id=?", [id]); + return db.rawQuery("SELECT * FROM RoomStates WHERE room_id=?", [id]); } Future forgetRoom(String roomID) async { @@ -483,7 +484,7 @@ class Store { 'UNIQUE(event_id))', /// The database scheme for room states. - 'State': 'CREATE TABLE IF NOT EXISTS State(' + + 'RoomStates': 'CREATE TABLE IF NOT EXISTS RoomStates(' + 'event_id TEXT PRIMARY KEY, ' + 'room_id TEXT, ' + 'origin_server_ts INTEGER, ' + From 3e62967852f56b7f0a53af0a61dfc17bfc68c86c Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:20:37 +0200 Subject: [PATCH 38/75] [Store] Fix last query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index e5a26cad..994e9c70 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -196,7 +196,7 @@ class Store { // Is the timeline limited? Then all previous messages should be // removed from the database! if (roomUpdate.limitedTimeline) { - txn.rawDelete("DELETE FROM Events WHERE chat_id=?", [roomUpdate.id]); + txn.rawDelete("DELETE FROM Events WHERE room_id=?", [roomUpdate.id]); txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE id=?", [roomUpdate.prev_batch, roomUpdate.id]); } From 442d7631fb46249113de99897fbc50a540d97b18 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:24:23 +0200 Subject: [PATCH 39/75] [Store] Fix query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 994e9c70..df619a70 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -197,7 +197,7 @@ class Store { // removed from the database! if (roomUpdate.limitedTimeline) { txn.rawDelete("DELETE FROM Events WHERE room_id=?", [roomUpdate.id]); - txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE id=?", + txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?", [roomUpdate.prev_batch, roomUpdate.id]); } return null; From 6416f5a33f102113e8881ef5cdfc53aa448ad462 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 8 Aug 2019 14:31:47 +0200 Subject: [PATCH 40/75] [Room] Default hero list --- lib/src/Room.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 7d7b1da5..ce67e25e 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -50,7 +50,7 @@ class Room { String prev_batch; - List mHeroes; + List mHeroes = []; int mJoinedMemberCount; int mInvitedMemberCount; From 9a7a4bfd71e808ab264754bfa0bfe4dae4e8a58e Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 26 Aug 2019 14:10:29 +0200 Subject: [PATCH 41/75] [Event] Remove meta because of coverage problems --- lib/src/RawEvent.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index c4bf7b5f..63a83574 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -23,7 +23,6 @@ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; -import 'package:meta/meta.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; @@ -57,8 +56,8 @@ class RawEvent { final Room room; RawEvent( - {@required this.content, - @required this.typeKey, + {this.content, + this.typeKey, this.eventId, this.roomId, this.senderId, From 453bdd0106956d16690bb8c9b47a65b195511a85 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 11:39:34 +0200 Subject: [PATCH 42/75] [Event] Fix User --- lib/src/RawEvent.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 63a83574..b6822e40 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -43,7 +43,7 @@ class RawEvent { /// The user who has sent this event if it is not a global account data event. final String senderId; - User get sender => room.states[senderId] ?? User(senderId); + User get sender => room.states[senderId].asUser ?? User(senderId); /// The time this event has received at the server. May be null for events like /// account data. From e5d4c6be732ade17872a2292410b4077904a41b4 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 12:04:05 +0200 Subject: [PATCH 43/75] [Store] Fix query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index 08038441..abc7b2f6 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -212,7 +212,7 @@ class Store { json.encode(userUpdate.content["content"]), ]); else if (userUpdate.type == "presence") - txn.rawInsert("INSERT OR REPLACE INTO Presence VALUES(?, ?)", [ + txn.rawInsert("INSERT OR REPLACE INTO Presences VALUES(?, ?)", [ userUpdate.eventType, userUpdate.content["sender"], json.encode(userUpdate.content["content"]), From eb2e30d730209777bea8c23db8b4474eda803c71 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 12:16:18 +0200 Subject: [PATCH 44/75] [Event] user null check --- lib/src/RawEvent.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index b6822e40..501c5a4a 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -43,7 +43,7 @@ class RawEvent { /// The user who has sent this event if it is not a global account data event. final String senderId; - User get sender => room.states[senderId].asUser ?? User(senderId); + User get sender => room.states[senderId]?.asUser ?? User(senderId); /// The time this event has received at the server. May be null for events like /// account data. From f90a996db66248ff5aa1e93481abf36675a7326f Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 12:26:42 +0200 Subject: [PATCH 45/75] [Store] Fix query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index abc7b2f6..baa8b427 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -212,7 +212,7 @@ class Store { json.encode(userUpdate.content["content"]), ]); else if (userUpdate.type == "presence") - txn.rawInsert("INSERT OR REPLACE INTO Presences VALUES(?, ?)", [ + txn.rawInsert("INSERT OR REPLACE INTO Presences VALUES(?, ?, ?)", [ userUpdate.eventType, userUpdate.content["sender"], json.encode(userUpdate.content["content"]), From c33350f7966cc89418ec36ef4b4000f36893dd5d Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 12:32:50 +0200 Subject: [PATCH 46/75] [Room] Heroes null check --- lib/src/Room.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index ce67e25e..88cc2fc5 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -71,7 +71,7 @@ class Room { return states["m.room.name"].content["name"]; if (canonicalAlias != null && !canonicalAlias.isEmpty) return canonicalAlias.substring(1, canonicalAlias.length).split(":")[0]; - if (mHeroes.length > 0) { + if (mHeroes != null && mHeroes.length > 0) { String displayname = ""; for (int i = 0; i < mHeroes.length; i++) { User hero = states[mHeroes[i]] != null @@ -93,7 +93,7 @@ class Room { MxContent get avatar { if (states["m.room.avatar"] != null) return MxContent(states["m.room.avatar"].content["url"]); - if (mHeroes.length == 1 && states[mHeroes[0]] != null) + if (mHeroes != null && mHeroes.length == 1 && states[mHeroes[0]] != null) return states[mHeroes[0]].asUser.avatarUrl; return MxContent(""); } From e54f134d928783f44d96286c5d155c2262467533 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 12:57:20 +0200 Subject: [PATCH 47/75] [Event] Fix getMapFromPayload --- lib/src/RawEvent.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 501c5a4a..478843d4 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -65,10 +65,11 @@ class RawEvent { this.unsigned, this.room}); - static Map getMapFromPayload(dynamic payload) => - payload is String - ? json.decode(payload) - : payload is Map ? payload : null; + static Map getMapFromPayload(dynamic payload) { + if (payload is String) return json.decode(payload); + if (payload is Map) return payload; + return null; + } /// Get a State event from a table row or from the event stream. factory RawEvent.fromJson(Map jsonPayload, Room room) { From 76a1e538c713b4b98cae38322233991ca7b7dde2 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 13:06:41 +0200 Subject: [PATCH 48/75] [Room] Fix room id --- lib/src/Room.dart | 2 +- test/Room_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 88cc2fc5..d87155d5 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -416,7 +416,7 @@ class Room { {Future>> states, Future>> roomAccountData}) async { Room newRoom = Room( - id: row["id"], + id: row["room_id"], membership: Membership.values .firstWhere((e) => e.toString() == 'Membership.' + row["membership"]), notificationCount: row["notification_count"], diff --git a/test/Room_test.dart b/test/Room_test.dart index 14c2fc95..cffb816d 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -63,7 +63,7 @@ void main() { ]; Map jsonObj = { - "id": id, + "room_id": id, "membership": membership.toString().split('.').last, "avatar_url": "", "notification_count": notificationCount, From b4f00945425043580a2cfa76138c3a1c8629de7e Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 28 Aug 2019 13:09:52 +0200 Subject: [PATCH 49/75] [Store] Fix query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index baa8b427..c2f15ba7 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -371,7 +371,7 @@ class Store { List roomList = []; for (num i = 0; i < res.length; i++) { Room room = await Room.getRoomFromTableRow(res[i], client, - states: getStatesFromRoomId(res[i]["id"])); + states: getStatesFromRoomId(res[i]["room_id"])); roomList.add(room); } return roomList; From c96d2af354f488a9f947e621358d19a4ad400c43 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 08:49:28 +0200 Subject: [PATCH 50/75] [Event] Add debugprint --- lib/src/RawEvent.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 478843d4..2fd6b529 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -66,6 +66,7 @@ class RawEvent { this.room}); static Map getMapFromPayload(dynamic payload) { + print("getMapFromPayload: $payload"); if (payload is String) return json.decode(payload); if (payload is Map) return payload; return null; From 3bfed389a28ef53aed3884849468acef5eef9dcc Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 08:51:35 +0200 Subject: [PATCH 51/75] [Event] Add empty string check --- lib/src/RawEvent.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 2fd6b529..66ea7a00 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -67,7 +67,7 @@ class RawEvent { static Map getMapFromPayload(dynamic payload) { print("getMapFromPayload: $payload"); - if (payload is String) return json.decode(payload); + if (payload is String && !payload.isEmpty) return json.decode(payload); if (payload is Map) return payload; return null; } From 7edffb1219ed92856f36d4933a2ca3af1d113f54 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 08:56:10 +0200 Subject: [PATCH 52/75] [Event] Better nullcheck --- lib/src/RawEvent.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 66ea7a00..4926bc79 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -67,7 +67,8 @@ class RawEvent { static Map getMapFromPayload(dynamic payload) { print("getMapFromPayload: $payload"); - if (payload is String && !payload.isEmpty) return json.decode(payload); + if (payload == null || (payload is String && payload.isEmpty)) return null; + if (payload is String) return json.decode(payload); if (payload is Map) return payload; return null; } From 6117606b28ec2b35f4dbf084c9f67c0925e0dd37 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 08:57:43 +0200 Subject: [PATCH 53/75] [Event] Return empty map --- lib/src/RawEvent.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 4926bc79..0c074949 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -66,11 +66,10 @@ class RawEvent { this.room}); static Map getMapFromPayload(dynamic payload) { - print("getMapFromPayload: $payload"); - if (payload == null || (payload is String && payload.isEmpty)) return null; + if (payload == null || payload == "") return {}; if (payload is String) return json.decode(payload); if (payload is Map) return payload; - return null; + return {}; } /// Get a State event from a table row or from the event stream. From 5df56ea12f07dc901382f2f477ab04d837724207 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:01:17 +0200 Subject: [PATCH 54/75] [Event] Add trycatch block --- lib/src/RawEvent.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart index 0c074949..57375886 100644 --- a/lib/src/RawEvent.dart +++ b/lib/src/RawEvent.dart @@ -66,8 +66,12 @@ class RawEvent { this.room}); static Map getMapFromPayload(dynamic payload) { - if (payload == null || payload == "") return {}; - if (payload is String) return json.decode(payload); + if (payload is String) + try { + return json.decode(payload); + } catch (e) { + return {}; + } if (payload is Map) return payload; return {}; } From 91ae18e760566b95c7537a2e807c54600b818feb Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:03:05 +0200 Subject: [PATCH 55/75] [Room] Fix roomID query --- lib/src/Room.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index d87155d5..8ac85698 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -363,7 +363,7 @@ class Room { client.connection.onEvent.add(eventUpdate); client.store.storeEventUpdate(eventUpdate); client.store.txn.rawUpdate( - "UPDATE Rooms SET prev_batch=? WHERE id=?", [resp["end"], id]); + "UPDATE Rooms SET prev_batch=? WHERE room_id=?", [resp["end"], id]); } return; }); From c5664bfa711c79c65284358657557b48b5a1eb24 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:12:55 +0200 Subject: [PATCH 56/75] [Event] Fix default status --- lib/src/Event.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/Event.dart b/lib/src/Event.dart index 02580b57..ea156359 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -37,8 +37,10 @@ class Event extends RoomState { /// 2=RECEIVED int status; + static const int defaultStatus = 2; + Event( - {this.status = 2, + {this.status = defaultStatus, dynamic content, String typeKey, String eventId, @@ -70,7 +72,7 @@ class Event extends RoomState { final Map prevContent = RawEvent.getMapFromPayload(jsonPayload['prev_content']); return Event( - status: jsonPayload['status'] ?? 1, + status: jsonPayload['status'] ?? defaultStatus, content: content, typeKey: jsonPayload['type'], eventId: jsonPayload['event_id'], From ae573850bdeb512fdba5a2d3bb8a33078454f621 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:16:07 +0200 Subject: [PATCH 57/75] [RoomList] Autosort --- lib/src/RoomList.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index d24a4247..6f439d53 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -68,6 +68,7 @@ class RoomList { this.onlyGroups = false}) { eventSub ??= client.connection.onEvent.stream.listen(_handleEventUpdate); roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate); + sort(); } Room getRoomByAlias(String alias) { @@ -148,9 +149,13 @@ class RoomList { sortAndUpdate(); } - sortAndUpdate() { + sort() { rooms?.sort((a, b) => b.timeCreated.toTimeStamp().compareTo(a.timeCreated.toTimeStamp())); + } + + sortAndUpdate() { + sort(); if (onUpdate != null) onUpdate(); } } From 7739e9ad01456b95af5bd3638558c97e4c873216 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:29:24 +0200 Subject: [PATCH 58/75] [Store] Fix loadContacts query --- lib/src/Store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Store.dart b/lib/src/Store.dart index c2f15ba7..15e021ab 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -311,7 +311,7 @@ class Store { /// except users who are in the Room with the ID [exceptRoomID]. Future> loadContacts({String exceptRoomID = ""}) async { List> res = await db.rawQuery( - "SELECT * FROM RoomStates WHERE state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key", + "SELECT * FROM RoomStates WHERE state_key LIKE '@%:%' AND state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key", [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) From 93b4efe004efb1a5aab3b31b72ccd2ad16fed032 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:50:04 +0200 Subject: [PATCH 59/75] [Room] LastEvent calculation fix --- lib/src/Room.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 8ac85698..0fa3e292 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -124,7 +124,14 @@ class Room { /// Must be one of [all, mention] String notificationSettings; - Event get lastEvent => states["m.room.message"]?.timelineEvent; + Event get lastEvent { + ChatTime lastTime = ChatTime(0); + Event lastEvent = null; + states.forEach((String key, RoomState value) { + if (value.time > lastTime) lastEvent = value.timelineEvent; + }); + return lastEvent; + } /// Your current client instance. final Client client; From 6d143241ebe0876c6556b83011067acf88143501 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 09:52:37 +0200 Subject: [PATCH 60/75] [Room] LastEvent calulcation fix --- lib/src/Room.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 0fa3e292..a6c86bd8 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -126,9 +126,12 @@ class Room { Event get lastEvent { ChatTime lastTime = ChatTime(0); - Event lastEvent = null; - states.forEach((String key, RoomState value) { - if (value.time > lastTime) lastEvent = value.timelineEvent; + Event lastEvent = Event(); + states.forEach((String key, RoomState state) { + if (state.time > lastTime) { + lastTime = state.time; + lastEvent = state.timelineEvent; + } }); return lastEvent; } From 19d06920e2c7e9bb0d8ec6c601675a6bba316421 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 10:05:17 +0200 Subject: [PATCH 61/75] [Room] LastEvent can be null --- lib/src/Room.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index a6c86bd8..7a777830 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -126,7 +126,7 @@ class Room { Event get lastEvent { ChatTime lastTime = ChatTime(0); - Event lastEvent = Event(); + Event lastEvent = null; states.forEach((String key, RoomState state) { if (state.time > lastTime) { lastTime = state.time; From a237b71da9999020769f6011cd345c6d1732c483 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 10:09:31 +0200 Subject: [PATCH 62/75] [RoomList] Add debug prints --- lib/src/RoomList.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 6f439d53..f7b1ce07 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -134,6 +134,7 @@ class RoomList { } void _handleEventUpdate(EventUpdate eventUpdate) { + print("_handleEventUpdate"); // Search the room in the rooms num j = 0; for (j = 0; j < rooms.length; j++) { @@ -141,10 +142,12 @@ class RoomList { } final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID); if (!found) return; + print("found!"); RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]); if (rooms[j].states[stateEvent.key] != null && rooms[j].states[stateEvent.key].time > stateEvent.time) return; + print("is a new eventupdate"); rooms[j].states[stateEvent.key] = stateEvent; sortAndUpdate(); } From 2061b96992207807d3e21b193cf148766cf1aa47 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 10:26:21 +0200 Subject: [PATCH 63/75] [RoomList] Dont handle account data --- lib/src/RoomList.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index f7b1ce07..aa9bbfa7 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -134,7 +134,7 @@ class RoomList { } void _handleEventUpdate(EventUpdate eventUpdate) { - print("_handleEventUpdate"); + if (eventUpdate.type != "timeline" && eventUpdate.type != "state") return; // Search the room in the rooms num j = 0; for (j = 0; j < rooms.length; j++) { @@ -142,12 +142,10 @@ class RoomList { } final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID); if (!found) return; - print("found!"); RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]); if (rooms[j].states[stateEvent.key] != null && rooms[j].states[stateEvent.key].time > stateEvent.time) return; - print("is a new eventupdate"); rooms[j].states[stateEvent.key] = stateEvent; sortAndUpdate(); } From cccc8d181ac60535e3d8d215266fbed975f15ca9 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 10:29:00 +0200 Subject: [PATCH 64/75] [RoomState] Fix asUser in State --- lib/src/RoomState.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/RoomState.dart b/lib/src/RoomState.dart index 3e13e37e..bb630ed7 100644 --- a/lib/src/RoomState.dart +++ b/lib/src/RoomState.dart @@ -36,7 +36,7 @@ class RoomState extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; - User get stateKeyUser => room.states[stateKey] ?? User(stateKey); + User get stateKeyUser => room.states[stateKey].asUser ?? User(stateKey); RoomState( {this.prevContent, From f885dfbecca123ca7017cde6d653792d22b6bf3f Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 10:49:07 +0200 Subject: [PATCH 65/75] [Room] Add isDirectChat boolean getter --- lib/src/Room.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 7a777830..6de3d2e0 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -121,6 +121,9 @@ class Room { return returnUserId; } + /// Wheither this is a direct chat or not + bool get isDirectChat => directChatMatrixID != null; + /// Must be one of [all, mention] String notificationSettings; From 37866359be024fc271dc556308cb0893410cc927 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:03:43 +0200 Subject: [PATCH 66/75] [Client] Directchatroom should exist --- lib/src/Client.dart | 14 ++++++++------ lib/src/RoomList.dart | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 90561afc..84ee5094 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -123,12 +123,14 @@ class Client { /// Returns the (first) room ID from the store which is a private chat with the user [userId]. /// Returns null if there is none. - String getDirectChatFromUserId(String userId) => - accountData["m.direct"] != null && - accountData["m.direct"].content[userId] is List && - accountData["m.direct"].content[userId].length > 0 - ? accountData["m.direct"].content[userId][0] - : null; + String getDirectChatFromUserId(String userId) => accountData["m.direct"] != + null && + accountData["m.direct"].content[userId] is List && + accountData["m.direct"].content[userId].length > 0 && + roomList.getRoomById(accountData["m.direct"].content[userId][0]) != + null + ? accountData["m.direct"].content[userId][0] + : null; /// Checks the supported versions of the Matrix protocol and the supported /// login types. Returns false if the server is not compatible with the diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index aa9bbfa7..9d976071 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -78,6 +78,13 @@ class RoomList { return null; } + Room getRoomById(String id) { + for (int j = 0; j < rooms.length; j++) { + if (rooms[j].id == id) return rooms[j]; + } + return null; + } + void _handleRoomUpdate(RoomUpdate chatUpdate) { // Update the chat list item. // Search the room in the rooms From c04a3debe4d8e45d71ec9e3980ae5243c82f2ed6 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:12:14 +0200 Subject: [PATCH 67/75] [Client] Fix directChat db --- lib/src/Client.dart | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 84ee5094..0cafa734 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -123,14 +123,22 @@ class Client { /// Returns the (first) room ID from the store which is a private chat with the user [userId]. /// Returns null if there is none. - String getDirectChatFromUserId(String userId) => accountData["m.direct"] != - null && - accountData["m.direct"].content[userId] is List && - accountData["m.direct"].content[userId].length > 0 && - roomList.getRoomById(accountData["m.direct"].content[userId][0]) != - null - ? accountData["m.direct"].content[userId][0] - : null; + String getDirectChatFromUserId(String userId) { + if (accountData["m.direct"] != null && + accountData["m.direct"].content[userId] is List && + accountData["m.direct"].content[userId].length > 0) { + if (roomList.getRoomById(accountData["m.direct"].content[userId][0]) != + null) return accountData["m.direct"].content[userId][0]; + (accountData["m.direct"].content[userId] as List) + .remove(accountData["m.direct"].content[userId][0]); + connection.jsonRequest( + type: HTTPType.PUT, + action: "/client/r0/user/${userID}/account_data/m.direct", + data: directChats); + return getDirectChatFromUserId(userId); + } + return null; + } /// Checks the supported versions of the Matrix protocol and the supported /// login types. Returns false if the server is not compatible with the From 58976c3b9c75077cc5847391a32e180fd19bd3aa Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:21:10 +0200 Subject: [PATCH 68/75] [Room] Fix tests --- lib/src/Room.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 6de3d2e0..1db278e8 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -131,7 +131,7 @@ class Room { ChatTime lastTime = ChatTime(0); Event lastEvent = null; states.forEach((String key, RoomState state) { - if (state.time > lastTime) { + if (state.time != null && state.time > lastTime) { lastTime = state.time; lastEvent = state.timelineEvent; } From 47b708b6ba044b15b4962ec2ec62ceedd23d11fb Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:23:51 +0200 Subject: [PATCH 69/75] [RoomState] User nullchecker --- lib/src/RoomState.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/RoomState.dart b/lib/src/RoomState.dart index bb630ed7..aa086d6f 100644 --- a/lib/src/RoomState.dart +++ b/lib/src/RoomState.dart @@ -36,7 +36,7 @@ class RoomState extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; - User get stateKeyUser => room.states[stateKey].asUser ?? User(stateKey); + User get stateKeyUser => room.states[stateKey]?.asUser ?? User(stateKey); RoomState( {this.prevContent, From 2314fa0e09d50c9e501cbf320f91e8ffd1b515b0 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:50:57 +0200 Subject: [PATCH 70/75] [Event] Remove RawEvent --- lib/famedlysdk.dart | 1 - lib/src/AccountData.dart | 4 +- lib/src/Event.dart | 7 +- lib/src/Presence.dart | 4 +- lib/src/RawEvent.dart | 170 ----------------------------------- lib/src/RoomAccountData.dart | 4 +- lib/src/RoomState.dart | 155 ++++++++++++++++++++++++++------ test/Event_test.dart | 1 - 8 files changed, 138 insertions(+), 208 deletions(-) delete mode 100644 lib/src/RawEvent.dart diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index 1be53247..a9a1a65b 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -33,7 +33,6 @@ export 'package:famedlysdk/src/utils/MxContent.dart'; export 'package:famedlysdk/src/Client.dart'; export 'package:famedlysdk/src/Connection.dart'; export 'package:famedlysdk/src/Event.dart'; -export 'package:famedlysdk/src/RawEvent.dart'; export 'package:famedlysdk/src/Room.dart'; export 'package:famedlysdk/src/RoomList.dart'; export 'package:famedlysdk/src/RoomState.dart'; diff --git a/lib/src/AccountData.dart b/lib/src/AccountData.dart index ccb90594..b3a0a96a 100644 --- a/lib/src/AccountData.dart +++ b/lib/src/AccountData.dart @@ -21,7 +21,7 @@ * along with famedlysdk. If not, see . */ -import 'package:famedlysdk/src/RawEvent.dart'; +import 'package:famedlysdk/src/RoomState.dart'; class AccountData { /// The json payload of the content. The content highly depends on the type. @@ -35,7 +35,7 @@ class AccountData { /// Get a State event from a table row or from the event stream. factory AccountData.fromJson(Map jsonPayload) { final Map content = - RawEvent.getMapFromPayload(jsonPayload['content']); + RoomState.getMapFromPayload(jsonPayload['content']); return AccountData(content: content, typeKey: jsonPayload['type']); } } diff --git a/lib/src/Event.dart b/lib/src/Event.dart index ea156359..25a9d869 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -26,7 +26,6 @@ import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; import './Room.dart'; -import './RawEvent.dart'; /// Defines a timeline event for a room. class Event extends RoomState { @@ -66,11 +65,11 @@ class Event extends RoomState { /// Get a State event from a table row or from the event stream. factory Event.fromJson(Map jsonPayload, Room room) { final Map content = - RawEvent.getMapFromPayload(jsonPayload['content']); + RoomState.getMapFromPayload(jsonPayload['content']); final Map unsigned = - RawEvent.getMapFromPayload(jsonPayload['unsigned']); + RoomState.getMapFromPayload(jsonPayload['unsigned']); final Map prevContent = - RawEvent.getMapFromPayload(jsonPayload['prev_content']); + RoomState.getMapFromPayload(jsonPayload['prev_content']); return Event( status: jsonPayload['status'] ?? defaultStatus, content: content, diff --git a/lib/src/Presence.dart b/lib/src/Presence.dart index 20145198..124087a7 100644 --- a/lib/src/Presence.dart +++ b/lib/src/Presence.dart @@ -22,7 +22,7 @@ */ import 'package:famedlysdk/src/AccountData.dart'; -import 'package:famedlysdk/src/RawEvent.dart'; +import 'package:famedlysdk/src/RoomState.dart'; class Presence extends AccountData { /// The user who has sent this event if it is not a global account data event. @@ -34,7 +34,7 @@ class Presence extends AccountData { /// Get a State event from a table row or from the event stream. factory Presence.fromJson(Map jsonPayload) { final Map content = - RawEvent.getMapFromPayload(jsonPayload['content']); + RoomState.getMapFromPayload(jsonPayload['content']); return Presence( content: content, typeKey: jsonPayload['type'], diff --git a/lib/src/RawEvent.dart b/lib/src/RawEvent.dart deleted file mode 100644 index 57375886..00000000 --- a/lib/src/RawEvent.dart +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * This file is part of famedlysdk. - * - * famedlysdk is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * famedlysdk is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with famedlysdk. If not, see . - */ - -import 'dart:convert'; -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/src/utils/ChatTime.dart'; -import './Room.dart'; - -class RawEvent { - /// The Matrix ID for this event in the format '$localpart:server.abc'. Please not - /// that account data, presence and other events may not have an eventId. - final String eventId; - - /// The json payload of the content. The content highly depends on the type. - final Map content; - - /// The type String of this event. For example 'm.room.message'. - final String typeKey; - - /// The ID of the room this event belongs to. - final String roomId; - - /// The user who has sent this event if it is not a global account data event. - final String senderId; - - User get sender => room.states[senderId]?.asUser ?? User(senderId); - - /// The time this event has received at the server. May be null for events like - /// account data. - final ChatTime time; - - /// Optional additional content for this event. - final Map unsigned; - - /// The room this event belongs to. May be null. - final Room room; - - RawEvent( - {this.content, - this.typeKey, - this.eventId, - this.roomId, - this.senderId, - this.time, - this.unsigned, - this.room}); - - static Map getMapFromPayload(dynamic payload) { - if (payload is String) - try { - return json.decode(payload); - } catch (e) { - return {}; - } - if (payload is Map) return payload; - return {}; - } - - /// Get a State event from a table row or from the event stream. - factory RawEvent.fromJson(Map jsonPayload, Room room) { - final Map content = - getMapFromPayload(jsonPayload['content']); - final Map unsigned = - getMapFromPayload(jsonPayload['unsigned']); - return RawEvent( - content: content, - typeKey: jsonPayload['type'], - eventId: jsonPayload['event_id'], - roomId: jsonPayload['room_id'], - senderId: jsonPayload['sender'], - time: ChatTime(jsonPayload['origin_server_ts']), - unsigned: unsigned, - room: room); - } - - /// Get the real type. - EventTypes get type { - switch (typeKey) { - case "m.room.avatar": - return EventTypes.RoomAvatar; - case "m.room.name": - return EventTypes.RoomName; - case "m.room.topic": - return EventTypes.RoomTopic; - case "m.room.Aliases": - return EventTypes.RoomAliases; - case "m.room.canonical_alias": - return EventTypes.RoomCanonicalAlias; - case "m.room.create": - return EventTypes.RoomCreate; - case "m.room.join_rules": - return EventTypes.RoomJoinRules; - case "m.room.member": - return EventTypes.RoomMember; - case "m.room.power_levels": - return EventTypes.RoomPowerLevels; - case "m.room.guest_access": - return EventTypes.GuestAccess; - case "m.room.history_visibility": - return EventTypes.HistoryVisibility; - case "m.room.message": - switch (content["msgtype"] ?? "m.text") { - case "m.text": - if (content.containsKey("m.relates_to")) { - return EventTypes.Reply; - } - return EventTypes.Text; - case "m.notice": - return EventTypes.Notice; - case "m.emote": - return EventTypes.Emote; - case "m.image": - return EventTypes.Image; - case "m.video": - return EventTypes.Video; - case "m.audio": - return EventTypes.Audio; - case "m.file": - return EventTypes.File; - case "m.location": - return EventTypes.Location; - } - } - return EventTypes.Unknown; - } -} - -enum EventTypes { - Text, - Emote, - Notice, - Image, - Video, - Audio, - File, - Location, - Reply, - RoomAliases, - RoomCanonicalAlias, - RoomCreate, - RoomJoinRules, - RoomMember, - RoomPowerLevels, - RoomName, - RoomTopic, - RoomAvatar, - GuestAccess, - HistoryVisibility, - Unknown, -} diff --git a/lib/src/RoomAccountData.dart b/lib/src/RoomAccountData.dart index 5b1f416b..919dcfa9 100644 --- a/lib/src/RoomAccountData.dart +++ b/lib/src/RoomAccountData.dart @@ -23,7 +23,7 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/AccountData.dart'; -import 'package:famedlysdk/src/RawEvent.dart'; +import 'package:famedlysdk/src/RoomState.dart'; class RoomAccountData extends AccountData { /// The user who has sent this event if it is not a global account data event. @@ -39,7 +39,7 @@ class RoomAccountData extends AccountData { factory RoomAccountData.fromJson( Map jsonPayload, Room room) { final Map content = - RawEvent.getMapFromPayload(jsonPayload['content']); + RoomState.getMapFromPayload(jsonPayload['content']); return RoomAccountData( content: content, typeKey: jsonPayload['type'], diff --git a/lib/src/RoomState.dart b/lib/src/RoomState.dart index aa086d6f..d1addab1 100644 --- a/lib/src/RoomState.dart +++ b/lib/src/RoomState.dart @@ -20,13 +20,41 @@ * You should have received a copy of the GNU General Public License * along with famedlysdk. If not, see . */ + +import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; - import './Room.dart'; -import './RawEvent.dart'; -class RoomState extends RawEvent { +class RoomState { + /// The Matrix ID for this event in the format '$localpart:server.abc'. Please not + /// that account data, presence and other events may not have an eventId. + final String eventId; + + /// The json payload of the content. The content highly depends on the type. + final Map content; + + /// The type String of this event. For example 'm.room.message'. + final String typeKey; + + /// The ID of the room this event belongs to. + final String roomId; + + /// The user who has sent this event if it is not a global account data event. + final String senderId; + + User get sender => room.states[senderId]?.asUser ?? User(senderId); + + /// The time this event has received at the server. May be null for events like + /// account data. + final ChatTime time; + + /// Optional additional content for this event. + final Map unsigned; + + /// The room this event belongs to. May be null. + final Room room; + /// Optional. The previous content for this state. /// This will be present only for state events appearing in the timeline. /// If this is not a state event, or there is no previous content, this key will be null. @@ -36,37 +64,37 @@ class RoomState extends RawEvent { /// the overwriting semantics for this piece of room state. final String stateKey; - User get stateKeyUser => room.states[stateKey]?.asUser ?? User(stateKey); - RoomState( - {this.prevContent, + {this.content, + this.typeKey, + this.eventId, + this.roomId, + this.senderId, + this.time, + this.unsigned, + this.prevContent, this.stateKey, - dynamic content, - String typeKey, - String eventId, - String roomId, - String senderId, - ChatTime time, - dynamic unsigned, - Room room}) - : super( - content: content, - typeKey: typeKey, - eventId: eventId, - roomId: roomId, - senderId: senderId, - time: time, - unsigned: unsigned, - room: room); + this.room}); + + static Map getMapFromPayload(dynamic payload) { + if (payload is String) + try { + return json.decode(payload); + } catch (e) { + return {}; + } + if (payload is Map) return payload; + return {}; + } /// Get a State event from a table row or from the event stream. factory RoomState.fromJson(Map jsonPayload, Room room) { final Map content = - RawEvent.getMapFromPayload(jsonPayload['content']); + RoomState.getMapFromPayload(jsonPayload['content']); final Map unsigned = - RawEvent.getMapFromPayload(jsonPayload['unsigned']); + RoomState.getMapFromPayload(jsonPayload['unsigned']); final Map prevContent = - RawEvent.getMapFromPayload(jsonPayload['prev_content']); + RoomState.getMapFromPayload(jsonPayload['prev_content']); return RoomState( stateKey: jsonPayload['state_key'], prevContent: prevContent, @@ -107,4 +135,79 @@ class RoomState extends RawEvent { time: time, unsigned: unsigned, room: room); + + /// Get the real type. + EventTypes get type { + switch (typeKey) { + case "m.room.avatar": + return EventTypes.RoomAvatar; + case "m.room.name": + return EventTypes.RoomName; + case "m.room.topic": + return EventTypes.RoomTopic; + case "m.room.Aliases": + return EventTypes.RoomAliases; + case "m.room.canonical_alias": + return EventTypes.RoomCanonicalAlias; + case "m.room.create": + return EventTypes.RoomCreate; + case "m.room.join_rules": + return EventTypes.RoomJoinRules; + case "m.room.member": + return EventTypes.RoomMember; + case "m.room.power_levels": + return EventTypes.RoomPowerLevels; + case "m.room.guest_access": + return EventTypes.GuestAccess; + case "m.room.history_visibility": + return EventTypes.HistoryVisibility; + case "m.room.message": + switch (content["msgtype"] ?? "m.text") { + case "m.text": + if (content.containsKey("m.relates_to")) { + return EventTypes.Reply; + } + return EventTypes.Text; + case "m.notice": + return EventTypes.Notice; + case "m.emote": + return EventTypes.Emote; + case "m.image": + return EventTypes.Image; + case "m.video": + return EventTypes.Video; + case "m.audio": + return EventTypes.Audio; + case "m.file": + return EventTypes.File; + case "m.location": + return EventTypes.Location; + } + } + return EventTypes.Unknown; + } +} + +enum EventTypes { + Text, + Emote, + Notice, + Image, + Video, + Audio, + File, + Location, + Reply, + RoomAliases, + RoomCanonicalAlias, + RoomCreate, + RoomJoinRules, + RoomMember, + RoomPowerLevels, + RoomName, + RoomTopic, + RoomAvatar, + GuestAccess, + HistoryVisibility, + Unknown, } diff --git a/test/Event_test.dart b/test/Event_test.dart index 980b589e..3d32bf48 100644 --- a/test/Event_test.dart +++ b/test/Event_test.dart @@ -24,7 +24,6 @@ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/src/RawEvent.dart'; import 'package:famedlysdk/src/RoomState.dart'; import 'package:flutter_test/flutter_test.dart'; From 95b20ea41c44097c6c7320ca66ac8b838abbb6eb Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 11:55:12 +0200 Subject: [PATCH 71/75] [State] Fix stateKeyUser getter --- lib/src/RoomState.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/RoomState.dart b/lib/src/RoomState.dart index d1addab1..105a1406 100644 --- a/lib/src/RoomState.dart +++ b/lib/src/RoomState.dart @@ -64,6 +64,8 @@ class RoomState { /// the overwriting semantics for this piece of room state. final String stateKey; + User get stateKeyUser => room.states[stateKey]?.asUser ?? User(stateKey); + RoomState( {this.content, this.typeKey, From 2802901eec3f1f0898ad3abd8a03191b6fc1804e Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 29 Aug 2019 12:28:50 +0200 Subject: [PATCH 72/75] [Room] Request history with users --- lib/src/Connection.dart | 8 ++++---- lib/src/Room.dart | 29 ++++++++++++++++++++++++++++- test/FakeMatrixApi.dart | 4 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index adc202e2..3253c211 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -52,9 +52,9 @@ class Connection { })); } - String get _syncFilters => '{"room":{"state":{"lazy_load_members":true}}}'; + static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}'; - String get _firstSyncFilters => + static String firstSyncFilters = '{"room":{"include_leave":true,"state":{"lazy_load_members":true}}}'; /// Handles the connection to the Matrix Homeserver. You can change this to a @@ -284,10 +284,10 @@ class Connection { Future _sync() async { if (client.isLogged() == false) return; - String action = "/client/r0/sync?filter=$_firstSyncFilters"; + String action = "/client/r0/sync?filter=$firstSyncFilters"; if (client.prevBatch != null) { - action = "/client/r0/sync?filter=$_syncFilters"; + action = "/client/r0/sync?filter=$syncFilters"; action += "&timeout=30000"; action += "&since=${client.prevBatch}"; } diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 1db278e8..9d39d79e 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -353,7 +353,7 @@ class Room { final dynamic resp = await client.connection.jsonRequest( type: HTTPType.GET, action: - "/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount"); + "/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Connection.syncFilters}"); if (resp is ErrorResponse) return; @@ -364,6 +364,33 @@ class Room { resp["chunk"].length > 0 && resp["end"] is String)) return; + if (resp["state"] is List) { + client.store?.transaction(() { + for (int i = 0; i < resp["state"].length; i++) { + EventUpdate eventUpdate = EventUpdate( + type: "state", + roomID: id, + eventType: resp["state"][i]["type"], + content: resp["state"][i], + ); + client.connection.onEvent.add(eventUpdate); + client.store.storeEventUpdate(eventUpdate); + } + return; + }); + if (client.store == null) { + for (int i = 0; i < resp["state"].length; i++) { + EventUpdate eventUpdate = EventUpdate( + type: "state", + roomID: id, + eventType: resp["state"][i]["type"], + content: resp["state"][i], + ); + client.connection.onEvent.add(eventUpdate); + } + } + } + List history = resp["chunk"]; client.store?.transaction(() { for (int i = 0; i < history.length; i++) { diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index 98a2ee50..315645d2 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -39,7 +39,7 @@ class FakeMatrixApi extends MockClient { method == "GET" ? request.url.queryParameters : request.body; var res = {}; - print("$method request to $action with Data: $data"); + //print("$method request to $action with Data: $data"); // Sync requests with timeout if (data is Map && data["timeout"] is String) { @@ -92,7 +92,7 @@ class FakeMatrixApi extends MockClient { "origin_server_ts": 1432735824653, "unsigned": {"age": 1234} }, - "/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100": + "/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100&filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D": (var req) => { "start": "t47429-4392820_219380_26003_2265", "end": "t47409-4357353_219380_26003_2265", From dddc9a23c6870b4012888f73d323d0ae0c39723c Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 2 Sep 2019 10:33:32 +0200 Subject: [PATCH 73/75] [TypeDef] Place all typedefs on the top --- lib/src/Client.dart | 6 +++--- lib/src/Connection.dart | 4 ++-- lib/src/RoomList.dart | 8 ++++---- lib/src/Timeline.dart | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 0cafa734..305538e7 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -37,6 +37,9 @@ import 'requests/SetPushersRequest.dart'; import 'responses/ErrorResponse.dart'; import 'responses/PushrulesResponse.dart'; +typedef AccountDataEventCB = void Function(AccountData accountData); +typedef PresenceCB = void Function(Presence presence); + /// Represents a Matrix client to communicate with a /// [Matrix](https://matrix.org) homeserver and is the entry point for this /// SDK. @@ -338,6 +341,3 @@ class Client { return resp; } } - -typedef AccountDataEventCB = void Function(AccountData accountData); -typedef PresenceCB = void Function(Presence presence); diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index 3253c211..b7819398 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -473,6 +473,8 @@ class Connection { } } +typedef _FutureVoidCallback = Future Function(); + class _LifecycleEventHandler extends WidgetsBindingObserver { _LifecycleEventHandler({this.resumeCallBack, this.suspendingCallBack}); @@ -494,6 +496,4 @@ class _LifecycleEventHandler extends WidgetsBindingObserver { } } -typedef _FutureVoidCallback = Future Function(); - enum LoginState { logged, loggedOut } diff --git a/lib/src/RoomList.dart b/lib/src/RoomList.dart index 9d976071..a26819ba 100644 --- a/lib/src/RoomList.dart +++ b/lib/src/RoomList.dart @@ -32,6 +32,10 @@ import 'User.dart'; import 'sync/EventUpdate.dart'; import 'sync/RoomUpdate.dart'; +typedef onRoomListUpdateCallback = void Function(); +typedef onRoomListInsertCallback = void Function(int insertID); +typedef onRoomListRemoveCallback = void Function(int insertID); + /// Represents a list of rooms for this client, which will automatically update /// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get /// the initial room list, use the store or create a RoomList instance by using @@ -167,7 +171,3 @@ class RoomList { if (onUpdate != null) onUpdate(); } } - -typedef onRoomListUpdateCallback = void Function(); -typedef onRoomListInsertCallback = void Function(int insertID); -typedef onRoomListRemoveCallback = void Function(int insertID); diff --git a/lib/src/Timeline.dart b/lib/src/Timeline.dart index 36e49079..aeb89dfd 100644 --- a/lib/src/Timeline.dart +++ b/lib/src/Timeline.dart @@ -28,6 +28,9 @@ import 'Room.dart'; import 'User.dart'; import 'sync/EventUpdate.dart'; +typedef onTimelineUpdateCallback = void Function(); +typedef onTimelineInsertCallback = void Function(int insertID); + /// Represents the timeline of a room. The callbacks [onUpdate], [onDelete], /// [onInsert] and [onResort] will be triggered automatically. The initial /// event list will be retreived when created by the [room.getTimeline] method. @@ -102,6 +105,3 @@ class Timeline { if (onUpdate != null) onUpdate(); } } - -typedef onTimelineUpdateCallback = void Function(); -typedef onTimelineInsertCallback = void Function(int insertID); From 7b4c51aae9f164a6af9977bc56c3f0d982933160 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 2 Sep 2019 10:55:46 +0200 Subject: [PATCH 74/75] [Room] Remove loadEvents --- lib/src/Room.dart | 10 ++-------- test/Room_test.dart | 5 ----- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index 9d39d79e..a83a876f 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -499,7 +499,8 @@ class Room { Future getTimeline( {onTimelineUpdateCallback onUpdate, onTimelineInsertCallback onInsert}) async { - List events = await loadEvents(); + List events = []; + if (client.store != null) events = await client.store.getEventList(this); return Timeline( room: this, events: events, @@ -508,13 +509,6 @@ class Room { ); } - /// Load all events for a given room from the store. This includes all - /// senders of those events, who will be added to the participants list. - Future> loadEvents() async { - if (client.store != null) return await client.store.getEventList(this); - return []; - } - /// Load all participants for a given room from the store. @deprecated Future> loadParticipants() async { diff --git a/test/Room_test.dart b/test/Room_test.dart index cffb816d..19d1eb06 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -235,11 +235,6 @@ void main() { expect(timeline.events, []); }); - test("loadEvents", () async { - final List events = await room.loadEvents(); - expect(events, []); - }); - test("getUserByMXID", () async { final User user = await room.getUserByMXID("@getme:example.com"); expect(user.stateKey, "@getme:example.com"); From 963866f4087f9898d6090c76e6e8e932007990bc Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 2 Sep 2019 12:09:30 +0200 Subject: [PATCH 75/75] [Room] Add getParticipants method --- lib/src/Room.dart | 11 +++++++++++ test/Room_test.dart | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index a83a876f..bb198d00 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -515,6 +515,17 @@ class Room { return await client.store.loadParticipants(this); } + /// Returns all participants for this room. With lazy loading this + /// list may not be complete. User [requestParticipants] in this + /// case. + List getParticipants() { + List userList = []; + for (var entry in states.entries) + if (entry.value.type == EventTypes.RoomMember) + userList.add(entry.value.asUser); + return userList; + } + /// Request the full list of participants from the server. The local list /// from the store is not complete if the client uses lazy loading. Future> requestParticipants() async { diff --git a/test/Room_test.dart b/test/Room_test.dart index 19d1eb06..e016f6be 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -225,6 +225,21 @@ void main() { expect(resp, {}); }); + test("getParticipants", () async { + room.states["@alice:test.abc"] = RoomState( + senderId: "@alice:test.abc", + typeKey: "m.room.member", + roomId: room.id, + room: room, + eventId: "12345", + time: ChatTime.now(), + content: {"displayname": "alice"}, + stateKey: "@alice:test.abc"); + final List userList = room.getParticipants(); + expect(userList.length, 1); + expect(userList[0].displayName, "alice"); + }); + test("addToDirectChat", () async { final dynamic resp = await room.addToDirectChat("Testname"); expect(resp, {});