service: Migrate the basics to sqflite
This commit is contained in:
parent
6ab4b4062c
commit
f8950d9fb3
@ -1,9 +1,9 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/db/media.dart';
|
||||
import 'package:moxxyv2/shared/cache.dart';
|
||||
import 'package:moxxyv2/shared/helpers.dart';
|
||||
import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
import 'package:moxxyv2/shared/models/media.dart';
|
||||
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
|
||||
|
||||
class ConversationService {
|
||||
@ -61,7 +61,7 @@ class ConversationService {
|
||||
bool? open,
|
||||
int? unreadCounter,
|
||||
String? avatarUrl,
|
||||
List<DBSharedMedium>? sharedMedia,
|
||||
List<SharedMedium>? sharedMedia,
|
||||
ChatState? chatState,
|
||||
bool? muted,
|
||||
}
|
||||
@ -91,7 +91,7 @@ class ConversationService {
|
||||
String jid,
|
||||
int unreadCounter,
|
||||
int lastChangeTimestamp,
|
||||
List<DBSharedMedium> sharedMedia,
|
||||
List<SharedMedium> sharedMedia,
|
||||
bool open,
|
||||
bool muted,
|
||||
) async {
|
||||
|
4
lib/service/database/constants.dart
Normal file
4
lib/service/database/constants.dart
Normal file
@ -0,0 +1,4 @@
|
||||
const conversationsTable = 'Conversations';
|
||||
const messsagesTable = 'Messages';
|
||||
const rosterTable = 'RosterItems';
|
||||
const mediaTable = 'SharedMedia';
|
@ -1,80 +0,0 @@
|
||||
import 'package:moxxyv2/service/db/conversation.dart';
|
||||
import 'package:moxxyv2/service/db/media.dart';
|
||||
import 'package:moxxyv2/service/db/message.dart';
|
||||
import 'package:moxxyv2/service/db/roster.dart';
|
||||
import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
import 'package:moxxyv2/shared/models/media.dart';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
import 'package:moxxyv2/shared/models/roster.dart';
|
||||
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
|
||||
|
||||
SharedMedium sharedMediumDbToModel(DBSharedMedium s) {
|
||||
return SharedMedium(
|
||||
s.id!,
|
||||
s.path,
|
||||
s.timestamp,
|
||||
mime: s.mime,
|
||||
);
|
||||
}
|
||||
|
||||
Conversation conversationDbToModel(DBConversation c, bool inRoster, String subscription, ChatState chatState) {
|
||||
final media = c.sharedMedia
|
||||
.map(sharedMediumDbToModel)
|
||||
.toList();
|
||||
// ignore: cascade_invocations
|
||||
media.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
return Conversation(
|
||||
c.title,
|
||||
c.lastMessageBody,
|
||||
c.avatarUrl,
|
||||
c.jid,
|
||||
c.unreadCounter,
|
||||
c.lastChangeTimestamp,
|
||||
media,
|
||||
c.id!,
|
||||
c.open,
|
||||
inRoster,
|
||||
subscription,
|
||||
c.muted,
|
||||
chatState,
|
||||
);
|
||||
}
|
||||
|
||||
RosterItem rosterDbToModel(DBRosterItem i) {
|
||||
return RosterItem(
|
||||
i.id!,
|
||||
i.avatarUrl,
|
||||
i.avatarHash,
|
||||
i.jid,
|
||||
i.title,
|
||||
i.subscription,
|
||||
i.ask,
|
||||
i.groups,
|
||||
);
|
||||
}
|
||||
|
||||
Message messageDbToModel(DBMessage m) {
|
||||
return Message(
|
||||
m.sender,
|
||||
m.body,
|
||||
m.timestamp,
|
||||
m.sid,
|
||||
m.id!,
|
||||
m.conversationJid,
|
||||
m.isMedia,
|
||||
m.isFileUploadNotification,
|
||||
originId: m.originId,
|
||||
received: m.received,
|
||||
displayed: m.displayed,
|
||||
acked: m.acked,
|
||||
mediaUrl: m.mediaUrl,
|
||||
mediaType: m.mediaType,
|
||||
thumbnailData: m.thumbnailData,
|
||||
thumbnailDimensions: m.thumbnailDimensions,
|
||||
srcUrl: m.srcUrl,
|
||||
quotes: m.quotes.value != null ? messageDbToModel(m.quotes.value!) : null,
|
||||
errorType: m.errorType,
|
||||
filename: m.filename,
|
||||
);
|
||||
}
|
@ -14,23 +14,23 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
body TEXT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
sid TEXT NOT NULL,
|
||||
conversation_jid TEXT NOT NULL,
|
||||
is_media INTEGER NOT NULL,
|
||||
is_file_upload_notification INTEGER NOT NULL,
|
||||
error_type INTEGER,
|
||||
media_url TEXT,
|
||||
media_type TEXT,
|
||||
thumbnail_data TEXT,
|
||||
thumbnail_dimensions TEXT,
|
||||
conversationJid TEXT NOT NULL,
|
||||
isMedia INTEGER NOT NULL,
|
||||
isFileUploadNotification INTEGER NOT NULL,
|
||||
errorType INTEGER,
|
||||
mediaUrl TEXT,
|
||||
mediaType TEXT,
|
||||
thumbnailData TEXT,
|
||||
thumbnailDimensions TEXT,
|
||||
dimensions TEXT,
|
||||
src_url TEXT,
|
||||
srcUrl TEXT,
|
||||
received INTEGER,
|
||||
displayed INTEGER,
|
||||
acked INTEGER,
|
||||
origin_id TEXT,
|
||||
originId TEXT,
|
||||
quote_id INTEGER,
|
||||
filename TEXT,
|
||||
FOREIGN KEY (quote_id) REFERENCES Messages (id),
|
||||
FOREIGN KEY (quote_id) REFERENCES Messages (id)
|
||||
)''',
|
||||
);
|
||||
|
||||
@ -41,12 +41,12 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL,
|
||||
last_change_timestamp INTEGER NOT NULL,
|
||||
unread_counter INTEGER NOT NULL,
|
||||
last_message_body TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
lastChangeTimestamp INTEGER NOT NULL,
|
||||
unreadCounter INTEGER NOT NULL,
|
||||
lastMessageBody TEXT NOT NULL,
|
||||
open INTEGER NOT NULL,
|
||||
muted INTEGER NOT NULL,
|
||||
muted INTEGER NOT NULL
|
||||
)''',
|
||||
);
|
||||
|
||||
@ -59,7 +59,7 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
mime TEXT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
conversation_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (conversation_id) REFERENCES Conversations (id),
|
||||
FOREIGN KEY (conversation_id) REFERENCES Conversations (id)
|
||||
)''',
|
||||
);
|
||||
|
||||
@ -70,9 +70,10 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
avatar_hash TEXT NOT NULL,
|
||||
avatarUrl TEXT NOT NULL,
|
||||
avatarHash TEXT NOT NULL,
|
||||
subscription TEXT NOT NULL,
|
||||
ask TEXT NOT NULL,
|
||||
ask TEXT NOT NULL
|
||||
)''',
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxyv2/service/database/conversion.dart';
|
||||
import 'package:moxxyv2/service/database/creation.dart';
|
||||
import 'package:moxxyv2/service/database/helpers.dart';
|
||||
import 'package:moxxyv2/service/roster.dart';
|
||||
import 'package:moxxyv2/shared/error_types.dart';
|
||||
import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
@ -39,38 +39,83 @@ class DatabaseService {
|
||||
|
||||
/// Loads all conversations from the database and adds them to the state and cache.
|
||||
Future<List<Conversation>> loadConversations() async {
|
||||
final conversationsRaw = await _isar.dBConversations.where().findAll();
|
||||
final conversationsRaw = await _db.query('Conversations',
|
||||
orderBy: 'lastChangeTimestamp DESC',
|
||||
);
|
||||
|
||||
_log.finest('Conversations: ${conversationsRaw.length}');
|
||||
|
||||
final tmp = List<Conversation>.empty(growable: true);
|
||||
for (final c in conversationsRaw) {
|
||||
await c.sharedMedia.load();
|
||||
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
|
||||
final conv = conversationDbToModel(
|
||||
c,
|
||||
rosterItem != null,
|
||||
rosterItem?.subscription ?? 'none',
|
||||
ChatState.gone,
|
||||
final id = c['id']! as int;
|
||||
|
||||
final sharedMediaRaw = await _db.query(
|
||||
'SharedMedia',
|
||||
where: 'conversation_id = ?',
|
||||
whereArgs: [id],
|
||||
orderBy: 'timestamp DESC',
|
||||
);
|
||||
final sharedMedia = sharedMediaRaw
|
||||
.map(SharedMedium.fromJson)
|
||||
.toList();
|
||||
final rosterItem = await GetIt.I.get<RosterService>()
|
||||
.getRosterItemByJid(c['jid']! as String);
|
||||
|
||||
tmp.add(
|
||||
Conversation.fromJson({
|
||||
...c,
|
||||
'muted': intToBool(c['muted']! as int),
|
||||
'open': intToBool(c['open']! as int),
|
||||
'sharedMedia': sharedMedia,
|
||||
'inRoster': rosterItem != null,
|
||||
'subscription': rosterItem?.subscription ?? 'none',
|
||||
'chatState': ConversationChatStateConverter().toJson(ChatState.gone),
|
||||
}),
|
||||
);
|
||||
tmp.add(conv);
|
||||
}
|
||||
|
||||
_log.finest(tmp.toString());
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// Load messages for [jid] from the database.
|
||||
Future<List<Message>> loadMessagesForJid(String jid) async {
|
||||
final rawMessages = await _isar.dBMessages.where().conversationJidEqualTo(jid).findAll();
|
||||
final rawMessages = await _db.query(
|
||||
'Messages',
|
||||
where: 'conversationJid = ?',
|
||||
whereArgs: [jid],
|
||||
orderBy: 'timestamp DESC',
|
||||
);
|
||||
|
||||
final messages = List<Message>.empty(growable: true);
|
||||
for (final m in rawMessages) {
|
||||
await m.quotes.load();
|
||||
Message? quotes;
|
||||
if (m['quote_id'] != null) {
|
||||
final rawQuote = await _db.query(
|
||||
'Messages',
|
||||
where: 'conversationJid = ?, id = ?',
|
||||
whereArgs: [jid, m['id']! as int],
|
||||
);
|
||||
quotes = Message.fromJson(rawQuote.first);
|
||||
}
|
||||
|
||||
final msg = messageDbToModel(m);
|
||||
messages.add(msg);
|
||||
messages.add(
|
||||
Message.fromJson({
|
||||
...m,
|
||||
'quotes': quotes,
|
||||
'received': intToBool(m['received']! as int),
|
||||
'displayed': intToBool(m['displayed']! as int),
|
||||
'acked': intToBool(m['acked']! as int),
|
||||
'isMedia': intToBool(m['isMedia']! as int),
|
||||
'isFileUploadNotification': intToBool(m['isFileUploadNotification']! as int),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
/// Updates the conversation with id [id] inside the database.
|
||||
Future<Conversation> updateConversation(int id, {
|
||||
String? lastMessageBody,
|
||||
@ -78,43 +123,60 @@ class DatabaseService {
|
||||
bool? open,
|
||||
int? unreadCounter,
|
||||
String? avatarUrl,
|
||||
List<DBSharedMedium>? sharedMedia,
|
||||
List<SharedMedium>? sharedMedia,
|
||||
ChatState? chatState,
|
||||
bool? muted,
|
||||
}
|
||||
) async {
|
||||
final c = (await _isar.dBConversations.get(id))!;
|
||||
await c.sharedMedia.load();
|
||||
final cd = (await _db.query(
|
||||
'Conversations',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
)).first;
|
||||
final c = Map<String, dynamic>.from(cd);
|
||||
|
||||
//await c.sharedMedia.load();
|
||||
if (lastMessageBody != null) {
|
||||
c.lastMessageBody = lastMessageBody;
|
||||
c['lastMessageBody'] = lastMessageBody;
|
||||
}
|
||||
if (lastChangeTimestamp != null) {
|
||||
c.lastChangeTimestamp = lastChangeTimestamp;
|
||||
c['lastChangeTimestamp'] = lastChangeTimestamp;
|
||||
}
|
||||
if (open != null) {
|
||||
c.open = open;
|
||||
c['open'] = boolToInt(open);
|
||||
}
|
||||
if (unreadCounter != null) {
|
||||
c.unreadCounter = unreadCounter;
|
||||
c['unreadCounter'] = unreadCounter;
|
||||
}
|
||||
if (avatarUrl != null) {
|
||||
c.avatarUrl = avatarUrl;
|
||||
c['avatarUrl'] = avatarUrl;
|
||||
}
|
||||
if (sharedMedia != null) {
|
||||
c.sharedMedia.addAll(sharedMedia);
|
||||
// TODO(PapaTutuWawa): Implement
|
||||
//c.sharedMedia.addAll(sharedMedia);
|
||||
}
|
||||
if (muted != null) {
|
||||
c.muted = muted;
|
||||
c['muted'] = muted;
|
||||
}
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBConversations.put(c);
|
||||
await c.sharedMedia.save();
|
||||
});
|
||||
await _db.update(
|
||||
'Conversations',
|
||||
c,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
|
||||
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', chatState ?? ChatState.gone);
|
||||
return conversation;
|
||||
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c['jid']! as String);
|
||||
return Conversation.fromJson({
|
||||
...c,
|
||||
'muted': intToBool(c['muted']! as int),
|
||||
'open': intToBool(c['open']! as int),
|
||||
// TODO(PapaTutuWawa): Implement
|
||||
'sharedMedia': <SharedMedium>[],
|
||||
'inRoster': rosterItem != null,
|
||||
'subscription': rosterItem?.subscription ?? 'none',
|
||||
'chatState': ConversationChatStateConverter().toJson(ChatState.gone),
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates a [Conversation] inside the database given the data. This is so that the
|
||||
@ -126,43 +188,67 @@ class DatabaseService {
|
||||
String jid,
|
||||
int unreadCounter,
|
||||
int lastChangeTimestamp,
|
||||
List<DBSharedMedium> sharedMedia,
|
||||
List<SharedMedium> sharedMedia,
|
||||
bool open,
|
||||
bool muted,
|
||||
) async {
|
||||
final c = DBConversation()
|
||||
..jid = jid
|
||||
..title = title
|
||||
..avatarUrl = avatarUrl
|
||||
..lastChangeTimestamp = lastChangeTimestamp
|
||||
..unreadCounter = unreadCounter
|
||||
..lastMessageBody = lastMessageBody
|
||||
..open = open
|
||||
..muted = muted;
|
||||
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(jid);
|
||||
final conversation = Conversation(
|
||||
title,
|
||||
lastMessageBody,
|
||||
avatarUrl,
|
||||
jid,
|
||||
unreadCounter,
|
||||
lastChangeTimestamp,
|
||||
sharedMedia,
|
||||
-1,
|
||||
open,
|
||||
rosterItem != null,
|
||||
rosterItem?.subscription ?? 'none',
|
||||
muted,
|
||||
ChatState.gone,
|
||||
);
|
||||
|
||||
c.sharedMedia.addAll(sharedMedia);
|
||||
// TODO(PapaTutuWawa): Handle shared media
|
||||
//c.sharedMedia.addAll(sharedMedia);
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBConversations.put(c);
|
||||
});
|
||||
|
||||
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
|
||||
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', ChatState.gone);
|
||||
return conversation;
|
||||
final map = conversation
|
||||
.toJson()
|
||||
..remove('id')
|
||||
..remove('chatState')
|
||||
..remove('sharedMedia')
|
||||
..remove('inRoster')
|
||||
..remove('subscription');
|
||||
return conversation.copyWith(
|
||||
id: await _db.insert(
|
||||
'Conversations',
|
||||
{
|
||||
...map,
|
||||
'open': boolToInt(conversation.open),
|
||||
'muted': boolToInt(conversation.muted),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Like [addConversationFromData] but for [SharedMedium].
|
||||
Future<DBSharedMedium> addSharedMediumFromData(String path, int timestamp, { String? mime }) async {
|
||||
final s = DBSharedMedium()
|
||||
..path = path
|
||||
..mime = mime
|
||||
..timestamp = timestamp;
|
||||
Future<SharedMedium> addSharedMediumFromData(String path, int timestamp, { String? mime }) async {
|
||||
final s = SharedMedium(
|
||||
-1,
|
||||
path,
|
||||
timestamp,
|
||||
mime: mime,
|
||||
);
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBSharedMediums.put(s);
|
||||
});
|
||||
|
||||
return s;
|
||||
final map = s
|
||||
.toJson()
|
||||
..remove('id');
|
||||
return s.copyWith(
|
||||
id: await _db.insert(
|
||||
'SharedMedia',
|
||||
map,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Same as [addConversationFromData] but for a [Message].
|
||||
@ -185,26 +271,30 @@ class DatabaseService {
|
||||
String? filename,
|
||||
}
|
||||
) async {
|
||||
final m = DBMessage()
|
||||
..conversationJid = conversationJid
|
||||
..timestamp = timestamp
|
||||
..body = body
|
||||
..sender = sender
|
||||
..isMedia = isMedia
|
||||
..mediaType = mediaType
|
||||
..mediaUrl = mediaUrl
|
||||
..srcUrl = srcUrl
|
||||
..sid = sid
|
||||
..thumbnailData = thumbnailData
|
||||
..thumbnailDimensions = thumbnailDimensions
|
||||
..received = false
|
||||
..displayed = false
|
||||
..acked = false
|
||||
..originId = originId
|
||||
..errorType = noError
|
||||
..isFileUploadNotification = isFileUploadNotification
|
||||
..filename = filename;
|
||||
final m = Message(
|
||||
sender,
|
||||
body,
|
||||
timestamp,
|
||||
sid,
|
||||
-1,
|
||||
conversationJid,
|
||||
isMedia,
|
||||
isFileUploadNotification,
|
||||
errorType: noError,
|
||||
mediaUrl: mediaUrl,
|
||||
mediaType: mediaType,
|
||||
thumbnailData: thumbnailData,
|
||||
thumbnailDimensions: thumbnailDimensions,
|
||||
srcUrl: srcUrl,
|
||||
received: false,
|
||||
displayed: false,
|
||||
acked: false,
|
||||
originId: originId,
|
||||
filename: filename,
|
||||
);
|
||||
|
||||
// TODO(PapaTutuWawa): Handle quotes
|
||||
/*
|
||||
if (quoteId != null) {
|
||||
final quotes = await getMessageByXmppId(quoteId, conversationJid);
|
||||
if (quotes != null) {
|
||||
@ -213,24 +303,63 @@ class DatabaseService {
|
||||
_log.warning('Failed to add quote for message with id $quoteId');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBMessages.put(m);
|
||||
});
|
||||
final map = m
|
||||
.toJson()
|
||||
..remove('id')
|
||||
..remove('quotes')
|
||||
..remove('isDownloading')
|
||||
..remove('isUploading');
|
||||
|
||||
final msg = messageDbToModel(m);
|
||||
return msg;
|
||||
Message? quotes;
|
||||
if (quoteId != null) {
|
||||
quotes = await getMessageByXmppId(quoteId, conversationJid);
|
||||
if (quotes != null) {
|
||||
map['quote_id'] = quoteId;
|
||||
} else {
|
||||
_log.warning('Failed to add quote for message with id $quoteId');
|
||||
}
|
||||
}
|
||||
|
||||
return m.copyWith(
|
||||
id: await _db.insert(
|
||||
'Messages',
|
||||
{
|
||||
...map,
|
||||
|
||||
'isMedia': boolToInt(m.isMedia),
|
||||
'isFileUploadNotification': boolToInt(m.isFileUploadNotification),
|
||||
'received': boolToInt(m.received),
|
||||
'displayed': boolToInt(m.displayed),
|
||||
'acked': boolToInt(m.acked),
|
||||
},
|
||||
),
|
||||
quotes: quotes,
|
||||
);
|
||||
}
|
||||
|
||||
Future<DBMessage?> getMessageByXmppId(String id, String conversationJid) async {
|
||||
return _isar.dBMessages.filter()
|
||||
.conversationJidEqualTo(conversationJid)
|
||||
.and()
|
||||
.group((q) => q
|
||||
.sidEqualTo(id)
|
||||
.or()
|
||||
.originIdEqualTo(id),
|
||||
).findFirst();
|
||||
Future<Message?> getMessageByXmppId(String id, String conversationJid) async {
|
||||
_log.finest('id: $id, conversationJid: $conversationJid');
|
||||
final messagesRaw = await _db.query(
|
||||
'Messages',
|
||||
where: 'conversationJid = ? AND (sid = ? or originId = ?)',
|
||||
whereArgs: [conversationJid, id, id],
|
||||
limit: 1,
|
||||
);
|
||||
|
||||
if (messagesRaw.isEmpty) return null;
|
||||
|
||||
// TODO(PapaTutuWawa): Load the quoted message
|
||||
final msg = messagesRaw.first;
|
||||
return Message.fromJson({
|
||||
...msg,
|
||||
'isMedia': intToBool(msg['isMedia']! as int),
|
||||
'isFileUploadNotification': intToBool(msg['isFileUploadNotification']! as int),
|
||||
'received': intToBool(msg['received']! as int),
|
||||
'displayed': intToBool(msg['displayed']! as int),
|
||||
'acked': intToBool(msg['acked']! as int),
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates the message item with id [id] inside the database.
|
||||
@ -244,52 +373,73 @@ class DatabaseService {
|
||||
bool? isFileUploadNotification,
|
||||
String? srcUrl,
|
||||
}) async {
|
||||
final i = (await _isar.dBMessages.get(id))!;
|
||||
final md = (await _db.query(
|
||||
'Messages',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
)).first;
|
||||
final m = Map<String, dynamic>.from(md);
|
||||
|
||||
if (mediaUrl != null) {
|
||||
i.mediaUrl = mediaUrl;
|
||||
m['mediaUrl'] = mediaUrl;
|
||||
}
|
||||
if (mediaType != null) {
|
||||
i.mediaType = mediaType;
|
||||
m['mediaType'] = mediaType;
|
||||
}
|
||||
if (received != null) {
|
||||
i.received = received;
|
||||
m['received'] = boolToInt(received);
|
||||
}
|
||||
if (displayed != null) {
|
||||
i.displayed = displayed;
|
||||
m['displayed'] = boolToInt(displayed);
|
||||
}
|
||||
if (acked != null) {
|
||||
i.acked = acked;
|
||||
m['acked'] = boolToInt(acked);
|
||||
}
|
||||
if (errorType != null) {
|
||||
i.errorType = errorType;
|
||||
m['errorType'] = errorType;
|
||||
}
|
||||
if (isFileUploadNotification != null) {
|
||||
i.isFileUploadNotification = isFileUploadNotification;
|
||||
m['isFileUploadNotification'] = boolToInt(isFileUploadNotification);
|
||||
}
|
||||
if (srcUrl != null) {
|
||||
i.srcUrl = srcUrl;
|
||||
m['srcUrl'] = srcUrl;
|
||||
}
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBMessages.put(i);
|
||||
});
|
||||
await i.quotes.load();
|
||||
await _db.update(
|
||||
'Messages',
|
||||
m,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
|
||||
final msg = messageDbToModel(i);
|
||||
return msg;
|
||||
return Message.fromJson({
|
||||
...m,
|
||||
'isMedia': intToBool(m['isMedia']! as int),
|
||||
'isFileUploadNotification': intToBool(m['isFileUploadNotification']! as int),
|
||||
'received': intToBool(m['received']! as int),
|
||||
'displayed': intToBool(m['displayed']! as int),
|
||||
'acked': intToBool(m['acked']! as int),
|
||||
});
|
||||
}
|
||||
|
||||
/// Loads roster items from the database
|
||||
Future<List<RosterItem>> loadRosterItems() async {
|
||||
final roster = await _isar.dBRosterItems.where().findAll();
|
||||
return roster.map(rosterDbToModel).toList();
|
||||
final items = await _db.query('RosterItems');
|
||||
|
||||
return items.map((item) {
|
||||
item['groups'] = <String>[];
|
||||
return RosterItem.fromJson(item);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Removes a roster item from the database and cache
|
||||
Future<void> removeRosterItem(int id) async {
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBRosterItems.delete(id);
|
||||
});
|
||||
await _db.delete(
|
||||
'RosterItems',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a roster item from data
|
||||
@ -304,21 +454,28 @@ class DatabaseService {
|
||||
List<String> groups = const [],
|
||||
}
|
||||
) async {
|
||||
final rosterItem = DBRosterItem()
|
||||
..jid = jid
|
||||
..title = title
|
||||
..avatarUrl = avatarUrl
|
||||
..avatarHash = avatarHash
|
||||
..subscription = subscription
|
||||
..ask = ask
|
||||
..groups = groups;
|
||||
// TODO(PapaTutuWawa): Handle groups
|
||||
final i = RosterItem(
|
||||
-1,
|
||||
avatarUrl,
|
||||
avatarHash,
|
||||
jid,
|
||||
title,
|
||||
subscription,
|
||||
ask,
|
||||
<String>[],
|
||||
);
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBRosterItems.put(rosterItem);
|
||||
});
|
||||
|
||||
final item = rosterDbToModel(rosterItem);
|
||||
return item;
|
||||
final map = i
|
||||
.toJson()
|
||||
..remove('id');
|
||||
|
||||
return i.copyWith(
|
||||
id: await _db.insert(
|
||||
'RosterItems',
|
||||
map,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates the roster item with id [id] inside the database.
|
||||
@ -332,31 +489,41 @@ class DatabaseService {
|
||||
List<String>? groups,
|
||||
}
|
||||
) async {
|
||||
final i = (await _isar.dBRosterItems.get(id))!;
|
||||
final id_ = (await _db.query(
|
||||
'RosterItems',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
)).first;
|
||||
final i = Map<String, dynamic>.from(id_);
|
||||
|
||||
if (avatarUrl != null) {
|
||||
i.avatarUrl = avatarUrl;
|
||||
i['avatarUrl'] = avatarUrl;
|
||||
}
|
||||
if (avatarHash != null) {
|
||||
i.avatarHash = avatarHash;
|
||||
i['avatarHash'] = avatarHash;
|
||||
}
|
||||
if (title != null) {
|
||||
i.title = title;
|
||||
i['title'] = title;
|
||||
}
|
||||
/*
|
||||
if (groups != null) {
|
||||
i.groups = groups;
|
||||
}
|
||||
*/
|
||||
if (subscription != null) {
|
||||
i.subscription = subscription;
|
||||
i['subscription'] = subscription;
|
||||
}
|
||||
if (ask != null) {
|
||||
i.ask = ask;
|
||||
i['ask'] = ask;
|
||||
}
|
||||
|
||||
await _isar.writeTxn(() async {
|
||||
await _isar.dBRosterItems.put(i);
|
||||
});
|
||||
|
||||
final item = rosterDbToModel(i);
|
||||
return item;
|
||||
await _db.update(
|
||||
'RosterItems',
|
||||
i,
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
return RosterItem.fromJson(i);
|
||||
}
|
||||
}
|
||||
|
2
lib/service/database/helpers.dart
Normal file
2
lib/service/database/helpers.dart
Normal file
@ -0,0 +1,2 @@
|
||||
int boolToInt(bool b) => b ? 0 : 1;
|
||||
bool intToBool(int i) => i == 0 ? false : true;
|
@ -1,29 +0,0 @@
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:moxxyv2/service/db/media.dart';
|
||||
|
||||
part 'conversation.g.dart';
|
||||
|
||||
@Collection()
|
||||
@Name('Conversation')
|
||||
class DBConversation {
|
||||
Id? id;
|
||||
|
||||
@Index(caseSensitive: false)
|
||||
late String jid;
|
||||
|
||||
late String title;
|
||||
|
||||
late String avatarUrl;
|
||||
|
||||
late int lastChangeTimestamp;
|
||||
|
||||
late int unreadCounter;
|
||||
|
||||
late String lastMessageBody;
|
||||
|
||||
late bool open;
|
||||
|
||||
late bool muted;
|
||||
|
||||
final sharedMedia = IsarLinks<DBSharedMedium>();
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'media.g.dart';
|
||||
|
||||
@Collection()
|
||||
@Name('SharedMedium')
|
||||
class DBSharedMedium {
|
||||
Id? id;
|
||||
|
||||
@Index(caseSensitive: true)
|
||||
late String path;
|
||||
|
||||
late String? mime;
|
||||
|
||||
// The timestamp of the corresponding message
|
||||
late int timestamp;
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
|
||||
@Collection()
|
||||
@Name('Message')
|
||||
class DBMessage {
|
||||
Id? id;
|
||||
|
||||
@Index(caseSensitive: false)
|
||||
late String conversationJid;
|
||||
|
||||
late int timestamp;
|
||||
|
||||
late String body;
|
||||
|
||||
/// The full JID of the sender
|
||||
@Index(caseSensitive: false)
|
||||
late String sender;
|
||||
|
||||
late String sid;
|
||||
String? originId;
|
||||
|
||||
/// Indicate if the message was received by the client (via Chat Markers or Delivery Receipts) or acked by the server
|
||||
late bool acked;
|
||||
late bool received;
|
||||
late bool displayed;
|
||||
|
||||
/// In case an error is related to the message, this stores an enum-like constant
|
||||
/// that clearly identifies the error.
|
||||
late int errorType;
|
||||
|
||||
/// If true, then the message is currently a placeholder for a File Upload Notification
|
||||
/// and may be replaced
|
||||
late bool isFileUploadNotification;
|
||||
|
||||
/// The message that this one quotes
|
||||
final quotes = IsarLink<DBMessage>();
|
||||
|
||||
/// Url a file can be accessed from
|
||||
String? srcUrl;
|
||||
/// A file:// URL pointing to the file
|
||||
String? mediaUrl;
|
||||
/// If the message should be treated as a media message, e.g. an image
|
||||
late bool isMedia;
|
||||
/// The mime type, if available
|
||||
String? mediaType;
|
||||
// TODO(Unknown): Add a flag to specify the thumbnail type
|
||||
/// The data of the thumbnail base64-encoded if needed. Currently assumed to be blurhash
|
||||
String? thumbnailData;
|
||||
/// The dimensions of the thumbnail
|
||||
String? thumbnailDimensions;
|
||||
|
||||
/// The filename of the file. Useful for when we don't have the full URL yet
|
||||
String? filename;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'roster.g.dart';
|
||||
|
||||
@Collection()
|
||||
@Name('RosterItem')
|
||||
class DBRosterItem {
|
||||
Id? id;
|
||||
|
||||
late String jid;
|
||||
|
||||
late String title;
|
||||
|
||||
late String avatarUrl;
|
||||
late String avatarHash;
|
||||
|
||||
late List<String> groups;
|
||||
|
||||
late String subscription;
|
||||
|
||||
late String ask;
|
||||
}
|
@ -14,7 +14,6 @@ import 'package:moxxyv2/service/connectivity.dart';
|
||||
import 'package:moxxyv2/service/connectivity_watcher.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/db/media.dart';
|
||||
import 'package:moxxyv2/service/helpers.dart';
|
||||
import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
|
||||
import 'package:moxxyv2/service/httpfiletransfer/httpfiletransfer.dart';
|
||||
@ -29,6 +28,7 @@ import 'package:moxxyv2/shared/eventhandler.dart';
|
||||
import 'package:moxxyv2/shared/events.dart';
|
||||
import 'package:moxxyv2/shared/helpers.dart';
|
||||
import 'package:moxxyv2/shared/migrator.dart';
|
||||
import 'package:moxxyv2/shared/models/media.dart';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
import 'package:moxxyv2/xmpp/connection.dart';
|
||||
import 'package:moxxyv2/xmpp/events.dart';
|
||||
@ -365,7 +365,7 @@ class XmppService {
|
||||
|
||||
// Create the shared media entries
|
||||
// Recipient -> [Shared Medium]
|
||||
final sharedMediaMap = <String, List<DBSharedMedium>>{};
|
||||
final sharedMediaMap = <String, List<SharedMedium>>{};
|
||||
final rs = GetIt.I.get<RosterService>();
|
||||
for (final recipient in recipients) {
|
||||
for (final path in paths) {
|
||||
@ -378,7 +378,7 @@ class XmppService {
|
||||
if (sharedMediaMap.containsKey(recipient)) {
|
||||
sharedMediaMap[recipient]!.add(medium);
|
||||
} else {
|
||||
sharedMediaMap[recipient] = List<DBSharedMedium>.from([medium]);
|
||||
sharedMediaMap[recipient] = List<SharedMedium>.from([medium]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,7 +596,7 @@ class XmppService {
|
||||
}
|
||||
|
||||
final msg = await ms.updateMessage(
|
||||
dbMsg.id!,
|
||||
dbMsg.id,
|
||||
received: true,
|
||||
);
|
||||
|
||||
@ -616,7 +616,7 @@ class XmppService {
|
||||
}
|
||||
|
||||
final msg = await ms.updateMessage(
|
||||
dbMsg.id!,
|
||||
dbMsg.id,
|
||||
received: dbMsg.received || event.type == 'received' || event.type == 'displayed',
|
||||
displayed: dbMsg.displayed || event.type == 'displayed',
|
||||
);
|
||||
@ -956,7 +956,7 @@ class XmppService {
|
||||
final ms = GetIt.I.get<MessageService>();
|
||||
final msg = await db.getMessageByXmppId(event.id, jid);
|
||||
if (msg != null) {
|
||||
await ms.updateMessage(msg.id!, acked: true);
|
||||
await ms.updateMessage(msg.id, acked: true);
|
||||
} else {
|
||||
_log.finest('Wanted to mark message as acked but did not find the message to ack');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user