From 0a46ec955187d3033c8ba066eb9afe8c61364523 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 2 Oct 2019 11:33:01 +0000 Subject: [PATCH] [Store] Make database private --- README.md | 22 +- lib/famedlysdk.dart | 3 +- lib/src/Client.dart | 13 +- lib/src/Connection.dart | 4 +- lib/src/Event.dart | 3 +- lib/src/Room.dart | 31 +- lib/src/Store.dart | 533 ---------------------------------- lib/src/StoreAPI.dart | 109 +++++++ lib/src/utils/MatrixFile.dart | 8 + pubspec.lock | 23 +- pubspec.yaml | 48 +-- test/Client_test.dart | 8 +- test/Room_test.dart | 9 +- 13 files changed, 177 insertions(+), 637 deletions(-) delete mode 100644 lib/src/Store.dart create mode 100644 lib/src/StoreAPI.dart create mode 100644 lib/src/utils/MatrixFile.dart diff --git a/README.md b/README.md index c9bbd4a..5b60c53 100644 --- a/README.md +++ b/README.md @@ -19,22 +19,36 @@ The API is documented here: [famedly.gitlab.io/famedlysdk](https://famedly.gitla ref: 77be6102f6cbb2e01adc28f9caa3aa583f914235 ``` +(Optional) Import the store + +```yaml + famedlysdk_store_sqflite: + git: + url: https://gitlab.com/famedly/libraries/famedlysdk-store.git + ref: 17fbbed1ea9b04ca041e9479d4e74bb4d2c78d55 +``` + ```dart import 'package:flutter/material.dart'; import 'package:famedlysdk/famedlysdk.dart'; - +import 'package:famedlysdk-store/ famedlysdk_store_sqflite.dart'; // Optional ``` -2. Access the MatrixState object by calling Matrix.of with your current BuildContext: +2. Create a new client: ```dart -Client matrix = Client("famedly talk"); +Client matrix = Client("HappyChat"); +``` + +Or with store: + +```dart +Client matrix = Client("HappyChat", store: Store(this)); ``` 3. Connect to a Matrix Homeserver and listen to the streams: ```dart - matrix.connection.onLoginStateChanged.stream.listen((bool loginState){ print("LoginState: ${loginState.toString()}"); }); diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index f31cbe2..a365d6d 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -30,6 +30,7 @@ export 'package:famedlysdk/src/sync/RoomUpdate.dart'; export 'package:famedlysdk/src/sync/EventUpdate.dart'; export 'package:famedlysdk/src/sync/UserUpdate.dart'; export 'package:famedlysdk/src/utils/ChatTime.dart'; +export 'package:famedlysdk/src/utils/MatrixFile.dart'; export 'package:famedlysdk/src/utils/MxContent.dart'; export 'package:famedlysdk/src/AccountData.dart'; export 'package:famedlysdk/src/Client.dart'; @@ -40,6 +41,6 @@ export 'package:famedlysdk/src/Room.dart'; export 'package:famedlysdk/src/RoomAccountData.dart'; export 'package:famedlysdk/src/RoomList.dart'; export 'package:famedlysdk/src/RoomState.dart'; -export 'package:famedlysdk/src/Store.dart'; +export 'package:famedlysdk/src/StoreAPI.dart'; export 'package:famedlysdk/src/Timeline.dart'; export 'package:famedlysdk/src/User.dart'; diff --git a/lib/src/Client.dart b/lib/src/Client.dart index 723ebfe..d3e253a 100644 --- a/lib/src/Client.dart +++ b/lib/src/Client.dart @@ -23,16 +23,17 @@ import 'dart:async'; import 'dart:core'; -import 'dart:io'; import 'package:famedlysdk/src/AccountData.dart'; import 'package:famedlysdk/src/Presence.dart'; +import 'package:famedlysdk/src/StoreAPI.dart'; import 'package:famedlysdk/src/sync/UserUpdate.dart'; +import 'package:famedlysdk/src/utils/MatrixFile.dart'; import 'Connection.dart'; import 'Room.dart'; import 'RoomList.dart'; -import 'Store.dart'; +//import 'Store.dart'; import 'User.dart'; import 'requests/SetPushersRequest.dart'; import 'responses/ErrorResponse.dart'; @@ -49,12 +50,12 @@ class Client { Connection connection; /// Optional persistent store for all data. - Store store; + StoreAPI store; - Client(this.clientName, {this.debug = false}) { + Client(this.clientName, {this.debug = false, this.store}) { connection = Connection(this); - if (this.clientName != "testclient") store = Store(this); + if (this.clientName != "testclient") store = null; //Store(this); connection.onLoginStateChanged.stream.listen((loginState) { print("LoginState: ${loginState.toString()}"); }); @@ -333,7 +334,7 @@ class Client { } /// Uploads a new user avatar for this user. Returns ErrorResponse if something went wrong. - Future setAvatar(File file) async { + Future setAvatar(MatrixFile file) async { final uploadResp = await connection.upload(file); if (uploadResp is ErrorResponse) return uploadResp; final setAvatarResp = await connection.jsonRequest( diff --git a/lib/src/Connection.dart b/lib/src/Connection.dart index c1fa08c..4742e32 100644 --- a/lib/src/Connection.dart +++ b/lib/src/Connection.dart @@ -24,10 +24,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:core'; -import 'dart:io'; import 'package:famedlysdk/src/Room.dart'; import 'package:famedlysdk/src/RoomList.dart'; +import 'package:famedlysdk/src/utils/MatrixFile.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:mime_type/mime_type.dart'; @@ -295,7 +295,7 @@ class Connection { /// Uploads a file with the name [fileName] as base64 encoded to the server /// and returns the mxc url as a string or an [ErrorResponse]. - Future upload(File file) async { + Future upload(MatrixFile file) async { List fileBytes; if (client.homeserver != "https://fakeServer.notExisting") fileBytes = await file.readAsBytes(); diff --git a/lib/src/Event.dart b/lib/src/Event.dart index 70aa402..b03c371 100644 --- a/lib/src/Event.dart +++ b/lib/src/Event.dart @@ -102,8 +102,7 @@ class Event extends RoomState { Future remove() async { if (status < 1) { if (room.client.store != null) - await room.client.store.db - .rawDelete("DELETE FROM Events WHERE event_id=?", [eventId]); + await room.client.store.removeEvent(eventId); room.client.connection.onEvent.add(EventUpdate( roomID: room.id, diff --git a/lib/src/Room.dart b/lib/src/Room.dart index a2ae91b..c30f6a0 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -21,7 +21,6 @@ * along with famedlysdk. If not, see . */ -import 'dart:io'; import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; import 'package:famedlysdk/src/RoomAccountData.dart'; @@ -29,8 +28,9 @@ 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'; +import 'package:famedlysdk/src/utils/MatrixFile.dart'; import 'package:famedlysdk/src/utils/MxContent.dart'; -import 'package:image/image.dart'; +//import 'package:image/image.dart'; import 'package:mime_type/mime_type.dart'; import './User.dart'; @@ -250,19 +250,19 @@ class Room { Future sendTextEvent(String message, {String txid = null}) => sendEvent({"msgtype": "m.text", "body": message}, txid: txid); - Future sendFileEvent(File file, String msgType, + Future sendFileEvent(MatrixFile file, String msgType, {String txid = null}) async { + String fileName = file.path.split("/").last; // Try to get the size of the file int size; try { - size = (await file.readAsBytes()).length; + size = file.bytes.length; } catch (e) { print("[UPLOAD] Could not get size. Reason: ${e.toString()}"); } // Upload file - String fileName = file.path.split("/").last; - String mimeType = mime(fileName); + String mimeType = mime(file.path); final dynamic uploadResp = await client.connection.upload(file); if (uploadResp is ErrorResponse) return null; @@ -284,12 +284,9 @@ class Room { return await sendEvent(content, txid: txid); } - Future sendImageEvent(File file, {String txid = null}) async { - String path = file.path; - String fileName = path.split("/").last; - - Image image = await decodeImage(await file.readAsBytes()); - + Future sendImageEvent(MatrixFile file, + {String txid = null, int width, int height}) async { + String fileName = file.path.split("/").last; final dynamic uploadResp = await client.connection.upload(file); if (uploadResp is ErrorResponse) return null; Map content = { @@ -297,10 +294,10 @@ class Room { "body": fileName, "url": uploadResp, "info": { - "size": image.getBytes().length, - "mimetype": mime(file.path), - "w": image.width, - "h": image.height, + "size": file.bytes.length, + "mimetype": mime(fileName), + "w": width, + "h": height, }, }; return await sendEvent(content, txid: txid); @@ -719,7 +716,7 @@ class Room { /// Uploads a new user avatar for this room. Returns ErrorResponse if something went wrong /// and the event ID otherwise. - Future setAvatar(File file) async { + Future setAvatar(MatrixFile file) async { final uploadResp = await client.connection.upload(file); if (uploadResp is ErrorResponse) return uploadResp; final setAvatarResp = await client.connection.jsonRequest( diff --git a/lib/src/Store.dart b/lib/src/Store.dart deleted file mode 100644 index 817d95e..0000000 --- a/lib/src/Store.dart +++ /dev/null @@ -1,533 +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:async'; -import 'dart:convert'; -import 'dart:core'; - -import 'package:famedlysdk/src/AccountData.dart'; -import 'package:famedlysdk/src/Presence.dart'; -import 'package:famedlysdk/src/RoomState.dart'; -import 'package:path/path.dart' as p; -import 'package:sqflite/sqflite.dart'; - -import 'Client.dart'; -import 'Connection.dart'; -import 'Event.dart'; -import 'Room.dart'; -import 'User.dart'; -import 'sync/EventUpdate.dart'; -import 'sync/RoomUpdate.dart'; -import 'sync/UserUpdate.dart'; - -/// Responsible to store all data persistent and to query objects from the -/// database. -class Store { - final Client client; - - Store(this.client) { - _init(); - } - - Database _db; - - /// SQLite database for all persistent data. It is recommended to extend this - /// SDK instead of writing direct queries to the database. - Database get db => _db; - - _init() async { - var databasePath = await getDatabasesPath(); - String path = p.join(databasePath, "FluffyMatrix.db"); - _db = await openDatabase(path, version: 14, - onCreate: (Database db, int version) async { - await createTables(db); - }, onUpgrade: (Database db, int oldVersion, int newVersion) async { - if (client.debug) - print( - "[Store] Migrate databse from version $oldVersion to $newVersion"); - if (oldVersion != newVersion) { - await schemes.forEach((String name, String scheme) async { - if (name != "Clients") await db.execute("DROP TABLE IF EXISTS $name"); - }); - await createTables(db); - await db.rawUpdate("UPDATE Clients SET prev_batch='' WHERE client=?", - [client.clientName]); - } - }); - - await _db.rawUpdate("UPDATE Events SET status=-1 WHERE status=0"); - - List list = await _db - .rawQuery("SELECT * FROM Clients WHERE client=?", [client.clientName]); - if (list.length == 1) { - var clientList = list[0]; - client.connection.connect( - newToken: clientList["token"], - newHomeserver: clientList["homeserver"], - newUserID: clientList["matrix_id"], - newDeviceID: clientList["device_id"], - newDeviceName: clientList["device_name"], - newLazyLoadMembers: clientList["lazy_load_members"] == 1, - newMatrixVersions: clientList["matrix_versions"].toString().split(","), - newPrevBatch: clientList["prev_batch"].toString().isEmpty - ? null - : clientList["prev_batch"], - ); - if (client.debug) - print("[Store] Restore client credentials of ${client.userID}"); - } else - client.connection.onLoginStateChanged.add(LoginState.loggedOut); - } - - Future createTables(Database db) async { - await schemes.forEach((String name, String scheme) async { - await db.execute(scheme); - }); - } - - Future queryPrevBatch() async { - List list = await txn.rawQuery( - "SELECT prev_batch FROM Clients WHERE client=?", [client.clientName]); - return list[0]["prev_batch"]; - } - - /// Will be automatically called when the client is logged in successfully. - Future storeClient() async { - await _db - .rawInsert('INSERT OR IGNORE INTO Clients VALUES(?,?,?,?,?,?,?,?,?)', [ - client.clientName, - client.accessToken, - client.homeserver, - client.userID, - client.deviceID, - client.deviceName, - client.prevBatch, - client.matrixVersions.join(","), - client.lazyLoadMembers, - ]); - return; - } - - /// Clears all tables from the database. - Future clear() async { - await _db - .rawDelete("DELETE FROM Clients WHERE client=?", [client.clientName]); - await schemes.forEach((String name, String scheme) async { - if (name != "Clients") await db.rawDelete("DELETE FROM $name"); - }); - return; - } - - Transaction txn; - - Future transaction(Future queries()) async { - return client.store.db.transaction((txnObj) async { - txn = txnObj; - await queries(); - }); - } - - /// Will be automatically called on every synchronisation. Must be called inside of - // /// [transaction]. - Future storePrevBatch(dynamic sync) { - txn.rawUpdate("UPDATE Clients SET prev_batch=? WHERE client=?", - [client.prevBatch, client.clientName]); - return null; - } - - Future storeRoomPrevBatch(Room room) async { - await _db.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?", - [room.prev_batch, room.id]); - return null; - } - - /// Stores a RoomUpdate object in the database. Must be called inside of - /// [transaction]. - 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, '') ", - [roomUpdate.id, roomUpdate.membership.toString().split('.').last]); - - // Update the notification counts and the limited timeline boolean and the summary - String updateQuery = - "UPDATE Rooms SET highlight_count=?, notification_count=?, membership=?"; - List updateArgs = [ - roomUpdate.highlight_count, - roomUpdate.notification_count, - roomUpdate.membership.toString().split('.').last - ]; - if (roomUpdate.summary?.mJoinedMemberCount != null) { - updateQuery += ", joined_member_count=?"; - updateArgs.add(roomUpdate.summary.mJoinedMemberCount); - } - if (roomUpdate.summary?.mInvitedMemberCount != null) { - updateQuery += ", invited_member_count=?"; - updateArgs.add(roomUpdate.summary.mInvitedMemberCount); - } - if (roomUpdate.summary?.mHeroes != null) { - updateQuery += ", heroes=?"; - updateArgs.add(roomUpdate.summary.mHeroes.join(",")); - } - updateQuery += " WHERE room_id=?"; - updateArgs.add(roomUpdate.id); - txn.rawUpdate(updateQuery, updateArgs); - - // Is the timeline limited? Then all previous messages should be - // 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 room_id=?", - [roomUpdate.prev_batch, roomUpdate.id]); - } - return null; - } - - /// Stores an UserUpdate object in the database. Must be called inside of - /// [transaction]. - Future storeUserEventUpdate(UserUpdate userUpdate) { - 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 Presences VALUES(?, ?, ?)", [ - userUpdate.eventType, - userUpdate.content["sender"], - json.encode(userUpdate.content["content"]), - ]); - return null; - } - - /// Stores an EventUpdate object in the database. Must be called inside of - /// [transaction]. - Future storeEventUpdate(EventUpdate eventUpdate) { - Map eventContent = eventUpdate.content; - 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"]; - - // 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=?, event_id=? WHERE event_id=?", [ - status, - eventContent["event_id"], - eventContent["unsigned"]["transaction_id"] - ]); - 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"]), - json.encode(eventContent["prevContent"]), - eventContent["state_key"], - status - ]); - - // Is there a transaction id? Then delete the event with this id. - if (status != -1 && - eventUpdate.content.containsKey("unsigned") && - eventUpdate.content["unsigned"]["transaction_id"] is String) - txn.rawDelete("DELETE FROM Events WHERE event_id=?", - [eventUpdate.content["unsigned"]["transaction_id"]]); - } - - if (type == "history") return null; - - if (eventUpdate.content["event_id"] != null) { - txn.rawInsert( - "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"], - chat_id, - json.encode(eventContent["content"]), - ]); - - return null; - } - - /// 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 RoomStates WHERE state_key=? AND room_id=?", - [matrixID, room.id]); - if (res.length != 1) return null; - return RoomState.fromJson(res[0], room).asUser; - } - - /// Loads all Users in the database to provide a contact list - /// 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 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++) - userList - .add(RoomState.fromJson(res[i], Room(id: "", client: client)).asUser); - return userList; - } - - /// Returns all users of a room by a given [roomID]. - Future> loadParticipants(Room room) async { - List> res = await db.rawQuery( - "SELECT * " + - " FROM RoomStates " + - " WHERE room_id=? " + - " AND type='m.room.member'", - [room.id]); - - List participants = []; - - for (num i = 0; i < res.length; i++) { - participants.add(RoomState.fromJson(res[i], room).asUser); - } - - return participants; - } - - /// 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 event_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)); - - return eventList; - } - - /// Returns all rooms, the client is participating. Excludes left rooms. - Future> getRoomList({bool onlyLeft = false}) async { - List> res = await db.rawQuery("SELECT * " + - " FROM Rooms" + - " WHERE membership" + - (onlyLeft ? "=" : "!=") + - "'leave' " + - " GROUP BY room_id "); - List roomList = []; - for (num i = 0; i < res.length; i++) { - Room room = await Room.getRoomFromTableRow(res[i], client, - states: getStatesFromRoomId(res[i]["room_id"])); - roomList.add(room); - } - return roomList; - } - - /// Returns a room without events and participants. - @deprecated - Future getRoomById(String id) async { - List> res = - 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)); - } - - Future>> getStatesFromRoomId(String id) async { - return db.rawQuery("SELECT * FROM RoomStates WHERE room_id=?", [id]); - } - - Future forgetRoom(String roomID) async { - await db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]); - return; - } - - /// Searches for the event in the store. - Future getEventById(String eventID, Room room) async { - List> res = await db.rawQuery( - "SELECT * FROM Events WHERE event_id=? AND room_id=?", - [eventID, room.id]); - if (res.length == 0) return null; - 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 { - assert(roomID != ""); - await db - .rawDelete("DELETE FROM NotificationsCache WHERE chat_id=?", [roomID]); - return; - } - - Future addNotification(String roomID, String event_id, int uniqueID) async { - assert(roomID != ""); - assert(event_id != ""); - assert(uniqueID != ""); - await db.rawInsert( - "INSERT INTO NotificationsCache(id, chat_id, event_id) VALUES (?, ?, ?)", - [uniqueID, roomID, event_id]); - // Make sure we got the same unique ID everywhere - await db.rawUpdate("UPDATE NotificationsCache SET id=? WHERE chat_id=?", - [uniqueID, roomID]); - return; - } - - Future>> getNotificationByRoom( - String room_id) async { - assert(room_id != ""); - List> res = await db.rawQuery( - "SELECT * FROM NotificationsCache WHERE chat_id=?", [room_id]); - if (res.length == 0) return null; - return res; - } - - static final Map schemes = { - /// The database scheme for the Client class. - "Clients": 'CREATE TABLE IF NOT EXISTS Clients(' + - 'client TEXT PRIMARY KEY, ' + - 'token TEXT, ' + - 'homeserver TEXT, ' + - 'matrix_id TEXT, ' + - 'device_id TEXT, ' + - 'device_name TEXT, ' + - 'prev_batch TEXT, ' + - 'matrix_versions TEXT, ' + - 'lazy_load_members INTEGER, ' + - 'UNIQUE(client))', - - /// The database scheme for the Room class. - 'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' + - 'room_id TEXT PRIMARY KEY, ' + - 'membership TEXT, ' + - 'highlight_count INTEGER, ' + - 'notification_count INTEGER, ' + - 'prev_batch TEXT, ' + - 'joined_member_count INTEGER, ' + - 'invited_member_count INTEGER, ' + - 'heroes TEXT, ' + - 'UNIQUE(room_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, ' + - 'type TEXT, ' + - 'unsigned TEXT, ' + - 'content TEXT, ' + - 'prev_content TEXT, ' + - 'state_key TEXT, ' + - "status INTEGER, " + - 'UNIQUE(event_id))', - - /// The database scheme for room states. - 'RoomStates': 'CREATE TABLE IF NOT EXISTS RoomStates(' + - '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. - 'Presences': 'CREATE TABLE IF NOT EXISTS Presences(' + - 'type TEXT PRIMARY KEY, ' + - 'sender TEXT, ' + - 'content TEXT, ' + - 'UNIQUE(sender))', - - /// The database scheme for the NotificationsCache class. - "NotificationsCache": 'CREATE TABLE IF NOT EXISTS NotificationsCache(' + - 'id int, ' + - 'chat_id TEXT, ' + // The chat id - 'event_id TEXT, ' + // The matrix id of the Event - 'UNIQUE(event_id))', - }; -} diff --git a/lib/src/StoreAPI.dart b/lib/src/StoreAPI.dart new file mode 100644 index 0000000..ed05a3a --- /dev/null +++ b/lib/src/StoreAPI.dart @@ -0,0 +1,109 @@ +/* + * 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:async'; +import 'dart:core'; +import 'package:famedlysdk/src/AccountData.dart'; +import 'package:famedlysdk/src/Presence.dart'; +import 'Client.dart'; +import 'Event.dart'; +import 'Room.dart'; +import 'User.dart'; +import 'sync/EventUpdate.dart'; +import 'sync/RoomUpdate.dart'; +import 'sync/UserUpdate.dart'; + +/// Responsible to store all data persistent and to query objects from the +/// database. +abstract class StoreAPI { + Client client; + + Future queryPrevBatch(); + + /// Will be automatically called when the client is logged in successfully. + Future storeClient(); + + /// Clears all tables from the database. + Future clear(); + + var txn; + + Future transaction(Future queries()); + + /// Will be automatically called on every synchronisation. Must be called inside of + // /// [transaction]. + Future storePrevBatch(dynamic sync); + + Future storeRoomPrevBatch(Room room); + + /// Stores a RoomUpdate object in the database. Must be called inside of + /// [transaction]. + Future storeRoomUpdate(RoomUpdate roomUpdate); + + /// Stores an UserUpdate object in the database. Must be called inside of + /// [transaction]. + Future storeUserEventUpdate(UserUpdate userUpdate); + + /// Stores an EventUpdate object in the database. Must be called inside of + /// [transaction]. + Future storeEventUpdate(EventUpdate eventUpdate); + + /// Returns a User object by a given Matrix ID and a Room. + Future getUser({String matrixID, Room room}); + + /// Loads all Users in the database to provide a contact list + /// except users who are in the Room with the ID [exceptRoomID]. + Future> loadContacts({String exceptRoomID = ""}); + + /// Returns all users of a room by a given [roomID]. + Future> loadParticipants(Room room); + + /// Returns a list of events for the given room and sets all participants. + Future> getEventList(Room room); + + /// Returns all rooms, the client is participating. Excludes left rooms. + Future> getRoomList({bool onlyLeft = false}); + + /// Returns a room without events and participants. + @deprecated + Future getRoomById(String id); + + Future>> getStatesFromRoomId(String id); + + Future forgetRoom(String roomID); + + /// Searches for the event in the store. + Future getEventById(String eventID, Room room); + + Future> getAccountData(); + + Future> getPresences(); + + Future removeEvent(String eventId); + + Future forgetNotification(String roomID); + + Future addNotification(String roomID, String event_id, int uniqueID); + + Future>> getNotificationByRoom(String room_id); +} diff --git a/lib/src/utils/MatrixFile.dart b/lib/src/utils/MatrixFile.dart new file mode 100644 index 0000000..5039614 --- /dev/null +++ b/lib/src/utils/MatrixFile.dart @@ -0,0 +1,8 @@ +class MatrixFile { + List bytes; + String path; + + MatrixFile({this.bytes, this.path}); + + Future> readAsBytes() async => bytes; +} diff --git a/pubspec.lock b/pubspec.lock index f8c56ee..a56a7af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,7 +126,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.3" csslib: dependency: transitive description: @@ -208,7 +208,7 @@ packages: source: hosted version: "3.1.3" image: - dependency: "direct main" + dependency: transitive description: name: image url: "https://pub.dartlang.org" @@ -220,7 +220,7 @@ packages: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.15.8" + version: "0.16.0" io: dependency: transitive description: @@ -306,7 +306,7 @@ packages: source: hosted version: "1.0.10" path: - dependency: "direct main" + dependency: transitive description: name: path url: "https://pub.dartlang.org" @@ -387,13 +387,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.5" - sqflite: - dependency: "direct main" - description: - name: sqflite - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6+4" stack_trace: dependency: transitive description: @@ -422,13 +415,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" - synchronized: - dependency: transitive - description: - name: synchronized - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" term_glyph: dependency: transitive description: @@ -494,4 +480,3 @@ packages: version: "2.1.16" sdks: dart: ">=2.4.0 <3.0.0" - flutter: ">=1.2.1 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 632ebee..ed1aa2c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,17 +11,9 @@ dependencies: flutter: sdk: flutter - # Database - sqflite: ^1.1.6+4 - path: ^1.6.2 - - # Connection http: ^0.12.0+2 mime_type: ^0.2.4 - image: ^2.1.4 - - # Time formatting - intl: ^0.15.8 + intl: ^0.16.0 json_annotation: ^2.4.0 @@ -33,40 +25,4 @@ dev_dependencies: json_serializable: ^3.0.0 pedantic: ^1.5.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL - -# For information on the generic Dart part of this file, see the -# following page: https://www.dartlang.org/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages +flutter: \ No newline at end of file diff --git a/test/Client_test.dart b/test/Client_test.dart index e084bf1..285d1a5 100644 --- a/test/Client_test.dart +++ b/test/Client_test.dart @@ -22,7 +22,6 @@ */ import 'dart:async'; -import 'dart:io'; import 'package:famedlysdk/src/AccountData.dart'; import 'package:famedlysdk/src/Client.dart'; @@ -35,6 +34,7 @@ import 'package:famedlysdk/src/responses/PushrulesResponse.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/sync/RoomUpdate.dart'; import 'package:famedlysdk/src/sync/UserUpdate.dart'; +import 'package:famedlysdk/src/utils/MatrixFile.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -272,13 +272,15 @@ void main() { }); test('upload', () async { - final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg")); + final MatrixFile testFile = + MatrixFile(bytes: [], path: "/root/file.jpeg"); final dynamic resp = await matrix.connection.upload(testFile); expect(resp, "mxc://example.com/AQwafuaFswefuhsfAFAgsw"); }); test('setAvatar', () async { - final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg")); + final MatrixFile testFile = + MatrixFile(bytes: [], path: "/root/file.jpeg"); final dynamic resp = await matrix.setAvatar(testFile); expect(resp, null); }); diff --git a/test/Room_test.dart b/test/Room_test.dart index 458c926..5d87181 100644 --- a/test/Room_test.dart +++ b/test/Room_test.dart @@ -21,8 +21,6 @@ * along with famedlysdk. If not, see . */ -import 'dart:io'; - import 'package:famedlysdk/src/Client.dart'; import 'package:famedlysdk/src/Event.dart'; import 'package:famedlysdk/src/Room.dart'; @@ -30,6 +28,7 @@ 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'; +import 'package:famedlysdk/src/utils/MatrixFile.dart'; import 'package:flutter_test/flutter_test.dart'; import 'FakeMatrixApi.dart'; @@ -259,7 +258,8 @@ void main() { }); test('setAvatar', () async { - final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg")); + final MatrixFile testFile = + MatrixFile(bytes: [], path: "/root/file.jpeg"); final dynamic resp = await room.setAvatar(testFile); expect(resp, "YUwRidLecu:example.com"); }); @@ -286,7 +286,8 @@ void main() { });*/ test('sendFileEvent', () async { - final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg")); + final MatrixFile testFile = + MatrixFile(bytes: [], path: "/root/file.jpeg"); final dynamic resp = await room.sendFileEvent(testFile, "m.file", txid: "testtxid"); expect(resp, "42");