Local message history db

This commit is contained in:
2024-06-23 12:29:07 +08:00
parent c8d23e7632
commit 34706531ad
7 changed files with 605 additions and 64 deletions

View File

@ -33,6 +33,7 @@ void main() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
if (PlatformInfo.isDesktop) {
await Window.initialize();
await Window.setEffect(

View File

@ -0,0 +1,50 @@
import 'package:get/get.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/message.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/message/history.dart';
Future<MessageHistoryDb> createHistoryDb() async {
return await $FloorMessageHistoryDb
.databaseBuilder('messaging_data.dart')
.build();
}
extension MessageHistoryHelper on MessageHistoryDb {
receiveMessage(Message remote) async {
await localMessages.insert(LocalMessage(
remote.id,
remote,
remote.channelId,
));
}
syncMessages(Channel channel, {String? scope}) async {
final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) return;
final client = auth.configureClient('messaging');
final resp = await client
.get('/api/channels/$scope/${channel.alias}/messages?take=10&offset=0');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
}
// TODO Continue sync until the last message / the message exists / sync limitation
final PaginationResult result = PaginationResult.fromJson(resp.body);
final parsed = result.data?.map((e) => Message.fromJson(e)).toList();
if (parsed != null) {
await localMessages.insertBulk(
parsed.map((x) => LocalMessage(x.id, x, x.channelId)).toList(),
);
}
}
Future<List<LocalMessage>> listMessages(Channel channel) async {
return await localMessages.findAllByChannel(channel.id);
}
}

View File

@ -0,0 +1,51 @@
import 'dart:async';
import 'dart:convert';
import 'package:floor/floor.dart';
import 'package:solian/models/message.dart';
import 'package:sqflite/sqflite.dart' as sqflite;
part 'history.g.dart';
@entity
class LocalMessage {
@primaryKey
final int id;
final Message data;
final int channelId;
LocalMessage(this.id, this.data, this.channelId);
}
class RemoteMessageConverter extends TypeConverter<Message, String> {
@override
Message decode(String databaseValue) {
return Message.fromJson(jsonDecode(databaseValue));
}
@override
String encode(Message value) {
return jsonEncode(value.toJson());
}
}
@dao
abstract class LocalMessageDao {
@Query('SELECT * FROM LocalMessage WHERE channelId = :channelId')
Future<List<LocalMessage>> findAllByChannel(int channelId);
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> insert(LocalMessage m);
@Insert(onConflict: OnConflictStrategy.replace)
Future<void> insertBulk(List<LocalMessage> m);
@Query('DELETE * FROM LocalMessage')
Future<void> wipeLocalMessages();
}
@TypeConverters([RemoteMessageConverter])
@Database(version: 1, entities: [LocalMessage])
abstract class MessageHistoryDb extends FloorDatabase {
LocalMessageDao get localMessages;
}

View File

@ -0,0 +1,165 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'history.dart';
// **************************************************************************
// FloorGenerator
// **************************************************************************
abstract class $MessageHistoryDbBuilderContract {
/// Adds migrations to the builder.
$MessageHistoryDbBuilderContract addMigrations(List<Migration> migrations);
/// Adds a database [Callback] to the builder.
$MessageHistoryDbBuilderContract addCallback(Callback callback);
/// Creates the database and initializes it.
Future<MessageHistoryDb> build();
}
// ignore: avoid_classes_with_only_static_members
class $FloorMessageHistoryDb {
/// Creates a database builder for a persistent database.
/// Once a database is built, you should keep a reference to it and re-use it.
static $MessageHistoryDbBuilderContract databaseBuilder(String name) =>
_$MessageHistoryDbBuilder(name);
/// Creates a database builder for an in memory database.
/// Information stored in an in memory database disappears when the process is killed.
/// Once a database is built, you should keep a reference to it and re-use it.
static $MessageHistoryDbBuilderContract inMemoryDatabaseBuilder() =>
_$MessageHistoryDbBuilder(null);
}
class _$MessageHistoryDbBuilder implements $MessageHistoryDbBuilderContract {
_$MessageHistoryDbBuilder(this.name);
final String? name;
final List<Migration> _migrations = [];
Callback? _callback;
@override
$MessageHistoryDbBuilderContract addMigrations(List<Migration> migrations) {
_migrations.addAll(migrations);
return this;
}
@override
$MessageHistoryDbBuilderContract addCallback(Callback callback) {
_callback = callback;
return this;
}
@override
Future<MessageHistoryDb> build() async {
final path = name != null
? await sqfliteDatabaseFactory.getDatabasePath(name!)
: ':memory:';
final database = _$MessageHistoryDb();
database.database = await database.open(
path,
_migrations,
_callback,
);
return database;
}
}
class _$MessageHistoryDb extends MessageHistoryDb {
_$MessageHistoryDb([StreamController<String>? listener]) {
changeListener = listener ?? StreamController<String>.broadcast();
}
LocalMessageDao? _localMessagesInstance;
Future<sqflite.Database> open(
String path,
List<Migration> migrations, [
Callback? callback,
]) async {
final databaseOptions = sqflite.OpenDatabaseOptions(
version: 1,
onConfigure: (database) async {
await database.execute('PRAGMA foreign_keys = ON');
await callback?.onConfigure?.call(database);
},
onOpen: (database) async {
await callback?.onOpen?.call(database);
},
onUpgrade: (database, startVersion, endVersion) async {
await MigrationAdapter.runMigrations(
database, startVersion, endVersion, migrations);
await callback?.onUpgrade?.call(database, startVersion, endVersion);
},
onCreate: (database, version) async {
await database.execute(
'CREATE TABLE IF NOT EXISTS `LocalMessage` (`id` INTEGER NOT NULL, `data` TEXT NOT NULL, `channelId` INTEGER NOT NULL, PRIMARY KEY (`id`))');
await callback?.onCreate?.call(database, version);
},
);
return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions);
}
@override
LocalMessageDao get localMessages {
return _localMessagesInstance ??=
_$LocalMessageDao(database, changeListener);
}
}
class _$LocalMessageDao extends LocalMessageDao {
_$LocalMessageDao(
this.database,
this.changeListener,
) : _queryAdapter = QueryAdapter(database),
_localMessageInsertionAdapter = InsertionAdapter(
database,
'LocalMessage',
(LocalMessage item) => <String, Object?>{
'id': item.id,
'data': _remoteMessageConverter.encode(item.data),
'channelId': item.channelId
});
final sqflite.DatabaseExecutor database;
final StreamController<String> changeListener;
final QueryAdapter _queryAdapter;
final InsertionAdapter<LocalMessage> _localMessageInsertionAdapter;
@override
Future<List<LocalMessage>> findAllByChannel(int channelId) async {
return _queryAdapter.queryList(
'SELECT * FROM LocalMessage WHERE channelId = ?1',
mapper: (Map<String, Object?> row) => LocalMessage(
row['id'] as int,
_remoteMessageConverter.decode(row['data'] as String),
row['channelId'] as int),
arguments: [channelId]);
}
@override
Future<void> wipeLocalMessages() async {
await _queryAdapter.queryNoReturn('DELETE * FROM LocalMessage');
}
@override
Future<void> insert(LocalMessage m) async {
await _localMessageInsertionAdapter.insert(m, OnConflictStrategy.replace);
}
@override
Future<void> insertBulk(List<LocalMessage> m) async {
await _localMessageInsertionAdapter.insertList(
m, OnConflictStrategy.replace);
}
}
// ignore_for_file: unused_element
final _remoteMessageConverter = RemoteMessageConverter();