[Store] Make database private

This commit is contained in:
Christian Pauly 2019-10-02 11:33:01 +00:00
parent ea83718682
commit 0a46ec9551
13 changed files with 177 additions and 637 deletions

View File

@ -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()}");
});

View File

@ -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';

View File

@ -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(

View File

@ -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();

View File

@ -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,

View File

@ -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(

View File

@ -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
View 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);
}

View File

@ -0,0 +1,8 @@
class MatrixFile {
List<int> bytes;
String path;
MatrixFile({this.bytes, this.path});
Future<List<int>> readAsBytes() async => bytes;
}

View File

@ -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"

View File

@ -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:

View File

@ -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);
});

View File

@ -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");