[StreamChatPersistence] Add initial implementation

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2021-01-25 19:14:55 +05:30
parent 1021814a75
commit 657b58af8b
47 changed files with 1878 additions and 1 deletions
@@ -0,0 +1,83 @@
import 'package:moor/isolate.dart';
import 'package:moor/moor.dart';
import 'package:stream_chat/stream_chat.dart';
import '../entity/entity.dart';
import '../dao/dao.dart';
import 'shared/shared_db.dart';
part 'moor_chat_database.g.dart';
LazyDatabase _openConnection(
String dbName, {
logStatements = false,
}) {
return LazyDatabase(() async {
return await SharedDB.constructDatabase(
dbName,
logStatements: logStatements,
);
});
}
///
@UseMoor(tables: [
Channels,
Messages,
Reactions,
Users,
Members,
Reads,
ChannelQueries,
ConnectionEvents,
], daos: [
UserDao,
ChannelDao,
MessageDao,
MemberDao,
ReactionDao,
ReadDao,
ChannelQueryDao,
ConnectionEventDao,
])
class MoorChatDatabase extends _$MoorChatDatabase {
/// Instantiate a new database instance
MoorChatDatabase(
String dbName, {
logStatements = false,
}) : super(_openConnection(
dbName,
logStatements: logStatements,
));
/// Instantiate a new database instance
MoorChatDatabase.connect(
this._isolate,
DatabaseConnection connection,
) : super.connect(connection);
MoorIsolate _isolate;
// you should bump this number whenever you change or add a table definition.
@override
int get schemaVersion => 1;
@override
MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (openingDetails, before, after) async {
if (before != after) {
final m = createMigrator();
for (final table in allTables) {
await m.deleteTable(table.actualTableName);
await m.createTable(table);
}
}
},
);
/// Closes the database instance
Future<void> disconnect() async {
await _isolate?.shutdownAll();
await close();
}
}
@@ -0,0 +1,87 @@
//ignore_for_file: public_member_api_docs
import 'dart:io';
import 'dart:isolate';
import 'package:moor/ffi.dart';
import 'package:moor/isolate.dart';
import 'package:moor/moor.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import '../moor_chat_database.dart';
class SharedDB {
static Future<VmDatabase> constructDatabase(
String dbName, {
bool logStatements = false,
}) async {
if (Platform.isIOS || Platform.isAndroid) {
final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, '$dbName.sqlite');
final file = File(path);
return VmDatabase(file, logStatements: logStatements);
}
if (Platform.isMacOS || Platform.isLinux) {
final file = File('$dbName.sqlite');
return VmDatabase(file, logStatements: logStatements);
}
return VmDatabase.memory(logStatements: logStatements);
}
static void _startBackground(_IsolateStartRequest request) {
final executor = LazyDatabase(() async {
return VmDatabase(
File(request.targetPath),
logStatements: request.logStatements,
);
});
final moorIsolate = MoorIsolate.inCurrent(
() => DatabaseConnection.fromExecutor(executor),
);
request.sendMoorIsolate.send(moorIsolate);
}
static Future<MoorIsolate> _createMoorIsolate(
String dbName, {
bool logStatements = false,
}) async {
final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, '$dbName.sqlite');
final receivePort = ReceivePort();
await Isolate.spawn(
_startBackground,
_IsolateStartRequest(
receivePort.sendPort,
path,
logStatements: logStatements,
),
);
return (await receivePort.first as MoorIsolate);
}
static Future<MoorChatDatabase> constructOfflineStorage(
String dbName, {
bool logStatements = false,
}) async {
final isolate = await _createMoorIsolate(
dbName,
logStatements: logStatements,
);
final connection = await isolate.connect();
return MoorChatDatabase.connect(isolate, connection);
}
}
class _IsolateStartRequest {
final SendPort sendMoorIsolate;
final String targetPath;
final bool logStatements;
const _IsolateStartRequest(
this.sendMoorIsolate,
this.targetPath, {
this.logStatements = false,
});
}
@@ -0,0 +1,3 @@
export 'unsupported_db.dart'
if (dart.library.io) 'native_db.dart' // implementation using dart:io
if (dart.library.html) 'web_db.dart';
@@ -0,0 +1,18 @@
//ignore_for_file: public_member_api_docs
//ignore_for_file: always_declare_return_types
class SharedDB {
static constructDatabase(
String dbName, {
bool logStatements = false,
}) {
throw 'Unsupported Platform';
}
static constructOfflineStorage(
String dbName, {
logStatements = false,
}) {
throw 'Unsupported Platform';
}
}
@@ -0,0 +1,21 @@
//ignore_for_file: public_member_api_docs
//ignore_for_file: always_declare_return_types
import 'package:moor/moor_web.dart';
import '../moor_chat_database.dart';
class SharedDB {
static constructDatabase(
String dbName, {
bool logStatements = false,
}) async {
return WebDatabase(dbName, logStatements: logStatements);
}
static Future<MoorChatDatabase> constructOfflineStorage(
String dbName, {
bool logStatements = false,
}) async {
return MoorChatDatabase(dbName, logStatements: logStatements);
}
}
@@ -0,0 +1,222 @@
import 'package:stream_chat/stream_chat.dart';
import 'moor_chat_database.dart';
import 'shared/shared_db.dart';
///
class StreamChatDatabaseImpl implements StreamChatDatabase {
///
StreamChatDatabaseImpl(
this._userId, {
Logger logger,
}) : _logger = logger,
assert(_userId != null);
final String _userId;
final Logger _logger;
MoorChatDatabase _db;
bool get _debugAssertConnected {
assert(() {
if (_db == null) {
throw Exception(
'A $runtimeType was used after being disconnected.\n'
'Once you have called disconnect() on a $runtimeType, it can no longer be used.',
);
}
return true;
}());
return true;
}
@override
Future<void> connect({
bool connectBackground = false,
bool logStatements = false,
}) async {
if (_db != null) {
throw Exception(
'An instance of StreamChatDatabase is already connected.\n'
'disconnect the previous instance before connecting again.',
);
}
final dbName = 'db_$_userId';
if (connectBackground) {
_logger?.info('Connecting on background isolate');
_db = await SharedDB.constructOfflineStorage(
dbName,
logStatements: logStatements,
);
} else {
_logger?.info('Connecting on a regular isolate');
_db = MoorChatDatabase(dbName, logStatements: logStatements);
}
}
@override
Future<Event> getConnectionInfo() {
return _db.connectionEventDao.connectionEvent;
}
@override
Future<void> updateConnectionInfo(Event event) {
return _db.connectionEventDao.updateConnectionEvent(event);
}
@override
Future<void> updateLastSyncAt(DateTime lastSyncAt) {
return _db.connectionEventDao.updateLastSyncAt(lastSyncAt);
}
@override
Future<DateTime> getLastSyncAt() {
return _db.connectionEventDao.lastSyncAt;
}
@override
Future<void> deleteChannelByCids(List<String> cids) {
return _db.channelDao.deleteChannelByCids(cids);
}
@override
Future<List<String>> getChannelCids() => _db.channelDao.cids;
@override
Future<void> deleteMessageByIds(List<String> messageIds) {
return _db.messageDao.deleteMessageByIds(messageIds);
}
@override
Future<void> deleteMessageByCids(List<String> cids) {
return _db.messageDao.deleteMessageByCids(cids);
}
@override
Future<List<Member>> getMembersByCid(String cid) {
return _db.memberDao.getMembersByCid(cid);
}
@override
Future<ChannelModel> getChannelByCid(String cid) {
return _db.channelDao.getChannelByCid(cid);
}
@override
Future<List<Message>> getMessagesByCid(
String cid, {
int limit = 20,
String messageLessThan,
String messageGreaterThan,
}) {
return _db.messageDao.getMessagesByCid(
cid,
limit: limit,
messageLessThan: messageLessThan,
messageGreaterThan: messageGreaterThan,
);
}
@override
Future<List<Read>> getReadsByCid(String cid) {
return _db.readDao.getReadsByCid(cid);
}
@override
Future<Map<String, List<Message>>> getChannelThreads(String cid) async {
final messages = await _db.messageDao.getThreadMessages(cid);
final messageByParentIdDictionary = <String, List<Message>>{};
for (final message in messages) {
final parentId = message.parentId;
messageByParentIdDictionary[parentId] = [
...messageByParentIdDictionary[parentId] ?? [],
message
];
}
return messageByParentIdDictionary;
}
@override
Future<List<Message>> getReplies(
String parentId, {
String lessThan,
}) {
return _db.messageDao.getThreadMessagesByParentId(
parentId,
lessThan: lessThan,
);
}
@override
Future<List<ChannelState>> getChannelStates({
Map<String, dynamic> filter,
List<SortOption> sort = const [],
PaginationParams paginationParams,
}) {
return _db.channelQueryDao.getChannelStates(
filter: filter,
sort: sort,
paginationParams: paginationParams,
);
}
@override
Future<void> updateChannelQueries(
Map<String, dynamic> filter,
List<String> cids,
bool clearQueryCache,
) {
return _db.channelQueryDao.updateChannelQueries(
filter,
cids,
clearQueryCache,
);
}
@override
Future<void> updateChannels(List<ChannelModel> channels) {
return _db.channelDao.updateChannels(channels);
}
@override
Future<void> updateMembers(String cid, List<Member> members) {
return _db.memberDao.updateMembers(cid, members);
}
@override
Future<void> updateMessages(String cid, List<Message> messages) {
return _db.messageDao.updateMessages(cid, messages);
}
@override
Future<void> updateReactions(List<Reaction> reactions) {
return _db.reactionDao.updateReactions(reactions);
}
@override
Future<void> updateReads(String cid, List<Read> reads) {
return _db.readDao.updateReads(cid, reads);
}
@override
Future<void> updateUsers(List<User> users) {
return _db.userDao.updateUsers(users);
}
@override
Future<void> disconnect({bool flush = false}) async {
if (_db != null) {
_logger?.info('Disconnecting');
if (flush) {
_logger?.info('Flushing');
await _db.batch((batch) {
_db.allTables.forEach((table) {
_db.delete(table).go();
});
});
}
await _db.disconnect();
_db = null;
}
}
}