[Store] Make database private
This commit is contained in:
parent
ea83718682
commit
0a46ec9551
22
README.md
22
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()}");
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<dynamic> setAvatar(File file) async {
|
||||
Future<dynamic> setAvatar(MatrixFile file) async {
|
||||
final uploadResp = await connection.upload(file);
|
||||
if (uploadResp is ErrorResponse) return uploadResp;
|
||||
final setAvatarResp = await connection.jsonRequest(
|
||||
|
|
|
@ -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<dynamic> upload(File file) async {
|
||||
Future<dynamic> upload(MatrixFile file) async {
|
||||
List<int> fileBytes;
|
||||
if (client.homeserver != "https://fakeServer.notExisting")
|
||||
fileBytes = await file.readAsBytes();
|
||||
|
|
|
@ -102,8 +102,7 @@ class Event extends RoomState {
|
|||
Future<bool> 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,
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String> sendTextEvent(String message, {String txid = null}) =>
|
||||
sendEvent({"msgtype": "m.text", "body": message}, txid: txid);
|
||||
|
||||
Future<String> sendFileEvent(File file, String msgType,
|
||||
Future<String> 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<String> sendImageEvent(File file, {String txid = null}) async {
|
||||
String path = file.path;
|
||||
String fileName = path.split("/").last;
|
||||
|
||||
Image image = await decodeImage(await file.readAsBytes());
|
||||
|
||||
Future<String> 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<String, dynamic> 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<dynamic> setAvatar(File file) async {
|
||||
Future<dynamic> setAvatar(MatrixFile file) async {
|
||||
final uploadResp = await client.connection.upload(file);
|
||||
if (uploadResp is ErrorResponse) return uploadResp;
|
||||
final setAvatarResp = await client.connection.jsonRequest(
|
||||
|
|
|
@ -1,533 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Map> 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<void> createTables(Database db) async {
|
||||
await schemes.forEach((String name, String scheme) async {
|
||||
await db.execute(scheme);
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> queryPrevBatch() async {
|
||||
List<Map> 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<void> 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<void> 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<void> transaction(Future<void> 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<void> storePrevBatch(dynamic sync) {
|
||||
txn.rawUpdate("UPDATE Clients SET prev_batch=? WHERE client=?",
|
||||
[client.prevBatch, client.clientName]);
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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<dynamic> 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<void> 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<void> storeEventUpdate(EventUpdate eventUpdate) {
|
||||
Map<String, dynamic> 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<String, dynamic> &&
|
||||
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<User> getUser({String matrixID, Room room}) async {
|
||||
List<Map<String, dynamic>> 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<List<User>> loadContacts({String exceptRoomID = ""}) async {
|
||||
List<Map<String, dynamic>> 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<User> 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<List<User>> loadParticipants(Room room) async {
|
||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||
"SELECT * " +
|
||||
" FROM RoomStates " +
|
||||
" WHERE room_id=? " +
|
||||
" AND type='m.room.member'",
|
||||
[room.id]);
|
||||
|
||||
List<User> 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<List<Event>> getEventList(Room room) async {
|
||||
List<Map<String, dynamic>> eventRes = await db.rawQuery(
|
||||
"SELECT * " +
|
||||
" FROM Events " +
|
||||
" WHERE room_id=?" +
|
||||
" GROUP BY event_id " +
|
||||
" ORDER BY origin_server_ts DESC",
|
||||
[room.id]);
|
||||
|
||||
List<Event> 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<List<Room>> getRoomList({bool onlyLeft = false}) async {
|
||||
List<Map<String, dynamic>> res = await db.rawQuery("SELECT * " +
|
||||
" FROM Rooms" +
|
||||
" WHERE membership" +
|
||||
(onlyLeft ? "=" : "!=") +
|
||||
"'leave' " +
|
||||
" GROUP BY room_id ");
|
||||
List<Room> 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<Room> getRoomById(String id) async {
|
||||
List<Map<String, dynamic>> 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<List<Map<String, dynamic>>> getStatesFromRoomId(String id) async {
|
||||
return db.rawQuery("SELECT * FROM RoomStates WHERE room_id=?", [id]);
|
||||
}
|
||||
|
||||
Future<void> forgetRoom(String roomID) async {
|
||||
await db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Searches for the event in the store.
|
||||
Future<Event> getEventById(String eventID, Room room) async {
|
||||
List<Map<String, dynamic>> 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<Map<String, AccountData>> getAccountData() async {
|
||||
Map<String, AccountData> newAccountData = {};
|
||||
List<Map<String, dynamic>> 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<Map<String, Presence>> getPresences() async {
|
||||
Map<String, Presence> newPresences = {};
|
||||
List<Map<String, dynamic>> 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<List<Map<String, dynamic>>> getNotificationByRoom(
|
||||
String room_id) async {
|
||||
assert(room_id != "");
|
||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||
"SELECT * FROM NotificationsCache WHERE chat_id=?", [room_id]);
|
||||
if (res.length == 0) return null;
|
||||
return res;
|
||||
}
|
||||
|
||||
static final Map<String, String> 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))',
|
||||
};
|
||||
}
|
109
lib/src/StoreAPI.dart
Normal file
109
lib/src/StoreAPI.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String> queryPrevBatch();
|
||||
|
||||
/// Will be automatically called when the client is logged in successfully.
|
||||
Future<void> storeClient();
|
||||
|
||||
/// Clears all tables from the database.
|
||||
Future<void> clear();
|
||||
|
||||
var txn;
|
||||
|
||||
Future<void> transaction(Future<void> queries());
|
||||
|
||||
/// Will be automatically called on every synchronisation. Must be called inside of
|
||||
// /// [transaction].
|
||||
Future<void> storePrevBatch(dynamic sync);
|
||||
|
||||
Future<void> storeRoomPrevBatch(Room room);
|
||||
|
||||
/// Stores a RoomUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeRoomUpdate(RoomUpdate roomUpdate);
|
||||
|
||||
/// Stores an UserUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeUserEventUpdate(UserUpdate userUpdate);
|
||||
|
||||
/// Stores an EventUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeEventUpdate(EventUpdate eventUpdate);
|
||||
|
||||
/// Returns a User object by a given Matrix ID and a Room.
|
||||
Future<User> 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<List<User>> loadContacts({String exceptRoomID = ""});
|
||||
|
||||
/// Returns all users of a room by a given [roomID].
|
||||
Future<List<User>> loadParticipants(Room room);
|
||||
|
||||
/// Returns a list of events for the given room and sets all participants.
|
||||
Future<List<Event>> getEventList(Room room);
|
||||
|
||||
/// Returns all rooms, the client is participating. Excludes left rooms.
|
||||
Future<List<Room>> getRoomList({bool onlyLeft = false});
|
||||
|
||||
/// Returns a room without events and participants.
|
||||
@deprecated
|
||||
Future<Room> getRoomById(String id);
|
||||
|
||||
Future<List<Map<String, dynamic>>> getStatesFromRoomId(String id);
|
||||
|
||||
Future<void> forgetRoom(String roomID);
|
||||
|
||||
/// Searches for the event in the store.
|
||||
Future<Event> getEventById(String eventID, Room room);
|
||||
|
||||
Future<Map<String, AccountData>> getAccountData();
|
||||
|
||||
Future<Map<String, Presence>> getPresences();
|
||||
|
||||
Future removeEvent(String eventId);
|
||||
|
||||
Future forgetNotification(String roomID);
|
||||
|
||||
Future addNotification(String roomID, String event_id, int uniqueID);
|
||||
|
||||
Future<List<Map<String, dynamic>>> getNotificationByRoom(String room_id);
|
||||
}
|
8
lib/src/utils/MatrixFile.dart
Normal file
8
lib/src/utils/MatrixFile.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
class MatrixFile {
|
||||
List<int> bytes;
|
||||
String path;
|
||||
|
||||
MatrixFile({this.bytes, this.path});
|
||||
|
||||
Future<List<int>> readAsBytes() async => bytes;
|
||||
}
|
23
pubspec.lock
23
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"
|
||||
|
|
48
pubspec.yaml
48
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:
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
|
|
Loading…
Reference in a new issue