[StreamChatPersistence] Add initial implementation
Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user