service: Migrate the basics to sqflite

This commit is contained in:
PapaTutuWawa 2022-09-07 22:10:55 +02:00
parent 6ab4b4062c
commit f8950d9fb3
11 changed files with 341 additions and 371 deletions

View File

@ -1,9 +1,9 @@
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:moxxyv2/service/database/database.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/cache.dart';
import 'package:moxxyv2/shared/helpers.dart'; import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/models/conversation.dart'; import 'package:moxxyv2/shared/models/conversation.dart';
import 'package:moxxyv2/shared/models/media.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart'; import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
class ConversationService { class ConversationService {
@ -61,7 +61,7 @@ class ConversationService {
bool? open, bool? open,
int? unreadCounter, int? unreadCounter,
String? avatarUrl, String? avatarUrl,
List<DBSharedMedium>? sharedMedia, List<SharedMedium>? sharedMedia,
ChatState? chatState, ChatState? chatState,
bool? muted, bool? muted,
} }
@ -91,7 +91,7 @@ class ConversationService {
String jid, String jid,
int unreadCounter, int unreadCounter,
int lastChangeTimestamp, int lastChangeTimestamp,
List<DBSharedMedium> sharedMedia, List<SharedMedium> sharedMedia,
bool open, bool open,
bool muted, bool muted,
) async { ) async {

View File

@ -0,0 +1,4 @@
const conversationsTable = 'Conversations';
const messsagesTable = 'Messages';
const rosterTable = 'RosterItems';
const mediaTable = 'SharedMedia';

View File

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

View File

@ -14,23 +14,23 @@ Future<void> createDatabase(Database db, int version) async {
body TEXT, body TEXT,
timestamp INTEGER NOT NULL, timestamp INTEGER NOT NULL,
sid TEXT NOT NULL, sid TEXT NOT NULL,
conversation_jid TEXT NOT NULL, conversationJid TEXT NOT NULL,
is_media INTEGER NOT NULL, isMedia INTEGER NOT NULL,
is_file_upload_notification INTEGER NOT NULL, isFileUploadNotification INTEGER NOT NULL,
error_type INTEGER, errorType INTEGER,
media_url TEXT, mediaUrl TEXT,
media_type TEXT, mediaType TEXT,
thumbnail_data TEXT, thumbnailData TEXT,
thumbnail_dimensions TEXT, thumbnailDimensions TEXT,
dimensions TEXT, dimensions TEXT,
src_url TEXT, srcUrl TEXT,
received INTEGER, received INTEGER,
displayed INTEGER, displayed INTEGER,
acked INTEGER, acked INTEGER,
origin_id TEXT, originId TEXT,
quote_id INTEGER, quote_id INTEGER,
filename TEXT, 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, id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL, jid TEXT NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
avatar_url TEXT NOT NULL, avatarUrl TEXT NOT NULL,
last_change_timestamp INTEGER NOT NULL, lastChangeTimestamp INTEGER NOT NULL,
unread_counter INTEGER NOT NULL, unreadCounter INTEGER NOT NULL,
last_message_body TEXT NOT NULL, lastMessageBody TEXT NOT NULL,
open INTEGER 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, mime TEXT,
timestamp INTEGER NOT NULL, timestamp INTEGER NOT NULL,
conversation_id 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, id INTEGER PRIMARY KEY AUTOINCREMENT,
jid TEXT NOT NULL, jid TEXT NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
avatar_hash TEXT NOT NULL, avatarUrl TEXT NOT NULL,
avatarHash TEXT NOT NULL,
subscription TEXT NOT NULL, subscription TEXT NOT NULL,
ask TEXT NOT NULL, ask TEXT NOT NULL
)''', )''',
); );
} }

View File

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:logging/logging.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/creation.dart';
import 'package:moxxyv2/service/database/helpers.dart';
import 'package:moxxyv2/service/roster.dart'; import 'package:moxxyv2/service/roster.dart';
import 'package:moxxyv2/shared/error_types.dart'; import 'package:moxxyv2/shared/error_types.dart';
import 'package:moxxyv2/shared/models/conversation.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. /// Loads all conversations from the database and adds them to the state and cache.
Future<List<Conversation>> loadConversations() async { 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); final tmp = List<Conversation>.empty(growable: true);
for (final c in conversationsRaw) { for (final c in conversationsRaw) {
await c.sharedMedia.load(); final id = c['id']! as int;
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid);
final conv = conversationDbToModel( final sharedMediaRaw = await _db.query(
c, 'SharedMedia',
rosterItem != null, where: 'conversation_id = ?',
rosterItem?.subscription ?? 'none', whereArgs: [id],
ChatState.gone, 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; return tmp;
} }
/// Load messages for [jid] from the database. /// Load messages for [jid] from the database.
Future<List<Message>> loadMessagesForJid(String jid) async { 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); final messages = List<Message>.empty(growable: true);
for (final m in rawMessages) { 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(
messages.add(msg); 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; return messages;
} }
/// Updates the conversation with id [id] inside the database. /// Updates the conversation with id [id] inside the database.
Future<Conversation> updateConversation(int id, { Future<Conversation> updateConversation(int id, {
String? lastMessageBody, String? lastMessageBody,
@ -78,43 +123,60 @@ class DatabaseService {
bool? open, bool? open,
int? unreadCounter, int? unreadCounter,
String? avatarUrl, String? avatarUrl,
List<DBSharedMedium>? sharedMedia, List<SharedMedium>? sharedMedia,
ChatState? chatState, ChatState? chatState,
bool? muted, bool? muted,
} }
) async { ) async {
final c = (await _isar.dBConversations.get(id))!; final cd = (await _db.query(
await c.sharedMedia.load(); 'Conversations',
where: 'id = ?',
whereArgs: [id],
)).first;
final c = Map<String, dynamic>.from(cd);
//await c.sharedMedia.load();
if (lastMessageBody != null) { if (lastMessageBody != null) {
c.lastMessageBody = lastMessageBody; c['lastMessageBody'] = lastMessageBody;
} }
if (lastChangeTimestamp != null) { if (lastChangeTimestamp != null) {
c.lastChangeTimestamp = lastChangeTimestamp; c['lastChangeTimestamp'] = lastChangeTimestamp;
} }
if (open != null) { if (open != null) {
c.open = open; c['open'] = boolToInt(open);
} }
if (unreadCounter != null) { if (unreadCounter != null) {
c.unreadCounter = unreadCounter; c['unreadCounter'] = unreadCounter;
} }
if (avatarUrl != null) { if (avatarUrl != null) {
c.avatarUrl = avatarUrl; c['avatarUrl'] = avatarUrl;
} }
if (sharedMedia != null) { if (sharedMedia != null) {
c.sharedMedia.addAll(sharedMedia); // TODO(PapaTutuWawa): Implement
//c.sharedMedia.addAll(sharedMedia);
} }
if (muted != null) { if (muted != null) {
c.muted = muted; c['muted'] = muted;
} }
await _isar.writeTxn(() async { await _db.update(
await _isar.dBConversations.put(c); 'Conversations',
await c.sharedMedia.save(); c,
}); where: 'id = ?',
whereArgs: [id],
);
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid); final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c['jid']! as String);
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', chatState ?? ChatState.gone); return Conversation.fromJson({
return conversation; ...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 /// Creates a [Conversation] inside the database given the data. This is so that the
@ -126,43 +188,67 @@ class DatabaseService {
String jid, String jid,
int unreadCounter, int unreadCounter,
int lastChangeTimestamp, int lastChangeTimestamp,
List<DBSharedMedium> sharedMedia, List<SharedMedium> sharedMedia,
bool open, bool open,
bool muted, bool muted,
) async { ) async {
final c = DBConversation() final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(jid);
..jid = jid final conversation = Conversation(
..title = title title,
..avatarUrl = avatarUrl lastMessageBody,
..lastChangeTimestamp = lastChangeTimestamp avatarUrl,
..unreadCounter = unreadCounter jid,
..lastMessageBody = lastMessageBody unreadCounter,
..open = open lastChangeTimestamp,
..muted = muted; 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 { final map = conversation
await _isar.dBConversations.put(c); .toJson()
}); ..remove('id')
..remove('chatState')
final rosterItem = await GetIt.I.get<RosterService>().getRosterItemByJid(c.jid); ..remove('sharedMedia')
final conversation = conversationDbToModel(c, rosterItem != null, rosterItem?.subscription ?? 'none', ChatState.gone); ..remove('inRoster')
return conversation; ..remove('subscription');
return conversation.copyWith(
id: await _db.insert(
'Conversations',
{
...map,
'open': boolToInt(conversation.open),
'muted': boolToInt(conversation.muted),
},
),
);
} }
/// Like [addConversationFromData] but for [SharedMedium]. /// Like [addConversationFromData] but for [SharedMedium].
Future<DBSharedMedium> addSharedMediumFromData(String path, int timestamp, { String? mime }) async { Future<SharedMedium> addSharedMediumFromData(String path, int timestamp, { String? mime }) async {
final s = DBSharedMedium() final s = SharedMedium(
..path = path -1,
..mime = mime path,
..timestamp = timestamp; timestamp,
mime: mime,
);
await _isar.writeTxn(() async { final map = s
await _isar.dBSharedMediums.put(s); .toJson()
}); ..remove('id');
return s.copyWith(
return s; id: await _db.insert(
'SharedMedia',
map,
),
);
} }
/// Same as [addConversationFromData] but for a [Message]. /// Same as [addConversationFromData] but for a [Message].
@ -185,26 +271,30 @@ class DatabaseService {
String? filename, String? filename,
} }
) async { ) async {
final m = DBMessage() final m = Message(
..conversationJid = conversationJid sender,
..timestamp = timestamp body,
..body = body timestamp,
..sender = sender sid,
..isMedia = isMedia -1,
..mediaType = mediaType conversationJid,
..mediaUrl = mediaUrl isMedia,
..srcUrl = srcUrl isFileUploadNotification,
..sid = sid errorType: noError,
..thumbnailData = thumbnailData mediaUrl: mediaUrl,
..thumbnailDimensions = thumbnailDimensions mediaType: mediaType,
..received = false thumbnailData: thumbnailData,
..displayed = false thumbnailDimensions: thumbnailDimensions,
..acked = false srcUrl: srcUrl,
..originId = originId received: false,
..errorType = noError displayed: false,
..isFileUploadNotification = isFileUploadNotification acked: false,
..filename = filename; originId: originId,
filename: filename,
);
// TODO(PapaTutuWawa): Handle quotes
/*
if (quoteId != null) { if (quoteId != null) {
final quotes = await getMessageByXmppId(quoteId, conversationJid); final quotes = await getMessageByXmppId(quoteId, conversationJid);
if (quotes != null) { if (quotes != null) {
@ -213,24 +303,63 @@ class DatabaseService {
_log.warning('Failed to add quote for message with id $quoteId'); _log.warning('Failed to add quote for message with id $quoteId');
} }
} }
*/
await _isar.writeTxn(() async { final map = m
await _isar.dBMessages.put(m); .toJson()
}); ..remove('id')
..remove('quotes')
..remove('isDownloading')
..remove('isUploading');
final msg = messageDbToModel(m); Message? quotes;
return msg; 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 { Future<Message?> getMessageByXmppId(String id, String conversationJid) async {
return _isar.dBMessages.filter() _log.finest('id: $id, conversationJid: $conversationJid');
.conversationJidEqualTo(conversationJid) final messagesRaw = await _db.query(
.and() 'Messages',
.group((q) => q where: 'conversationJid = ? AND (sid = ? or originId = ?)',
.sidEqualTo(id) whereArgs: [conversationJid, id, id],
.or() limit: 1,
.originIdEqualTo(id), );
).findFirst();
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. /// Updates the message item with id [id] inside the database.
@ -244,52 +373,73 @@ class DatabaseService {
bool? isFileUploadNotification, bool? isFileUploadNotification,
String? srcUrl, String? srcUrl,
}) async { }) 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) { if (mediaUrl != null) {
i.mediaUrl = mediaUrl; m['mediaUrl'] = mediaUrl;
} }
if (mediaType != null) { if (mediaType != null) {
i.mediaType = mediaType; m['mediaType'] = mediaType;
} }
if (received != null) { if (received != null) {
i.received = received; m['received'] = boolToInt(received);
} }
if (displayed != null) { if (displayed != null) {
i.displayed = displayed; m['displayed'] = boolToInt(displayed);
} }
if (acked != null) { if (acked != null) {
i.acked = acked; m['acked'] = boolToInt(acked);
} }
if (errorType != null) { if (errorType != null) {
i.errorType = errorType; m['errorType'] = errorType;
} }
if (isFileUploadNotification != null) { if (isFileUploadNotification != null) {
i.isFileUploadNotification = isFileUploadNotification; m['isFileUploadNotification'] = boolToInt(isFileUploadNotification);
} }
if (srcUrl != null) { if (srcUrl != null) {
i.srcUrl = srcUrl; m['srcUrl'] = srcUrl;
} }
await _isar.writeTxn(() async { await _db.update(
await _isar.dBMessages.put(i); 'Messages',
}); m,
await i.quotes.load(); where: 'id = ?',
whereArgs: [id],
);
final msg = messageDbToModel(i); return Message.fromJson({
return msg; ...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 /// Loads roster items from the database
Future<List<RosterItem>> loadRosterItems() async { Future<List<RosterItem>> loadRosterItems() async {
final roster = await _isar.dBRosterItems.where().findAll(); final items = await _db.query('RosterItems');
return roster.map(rosterDbToModel).toList();
return items.map((item) {
item['groups'] = <String>[];
return RosterItem.fromJson(item);
}).toList();
} }
/// Removes a roster item from the database and cache /// Removes a roster item from the database and cache
Future<void> removeRosterItem(int id) async { Future<void> removeRosterItem(int id) async {
await _isar.writeTxn(() async { await _db.delete(
await _isar.dBRosterItems.delete(id); 'RosterItems',
}); where: 'id = ?',
whereArgs: [id],
);
} }
/// Create a roster item from data /// Create a roster item from data
@ -304,21 +454,28 @@ class DatabaseService {
List<String> groups = const [], List<String> groups = const [],
} }
) async { ) async {
final rosterItem = DBRosterItem() // TODO(PapaTutuWawa): Handle groups
..jid = jid final i = RosterItem(
..title = title -1,
..avatarUrl = avatarUrl avatarUrl,
..avatarHash = avatarHash avatarHash,
..subscription = subscription jid,
..ask = ask title,
..groups = groups; subscription,
ask,
<String>[],
);
await _isar.writeTxn(() async { final map = i
await _isar.dBRosterItems.put(rosterItem); .toJson()
}); ..remove('id');
final item = rosterDbToModel(rosterItem); return i.copyWith(
return item; id: await _db.insert(
'RosterItems',
map,
),
);
} }
/// Updates the roster item with id [id] inside the database. /// Updates the roster item with id [id] inside the database.
@ -332,31 +489,41 @@ class DatabaseService {
List<String>? groups, List<String>? groups,
} }
) async { ) 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) { if (avatarUrl != null) {
i.avatarUrl = avatarUrl; i['avatarUrl'] = avatarUrl;
} }
if (avatarHash != null) { if (avatarHash != null) {
i.avatarHash = avatarHash; i['avatarHash'] = avatarHash;
} }
if (title != null) { if (title != null) {
i.title = title; i['title'] = title;
} }
/*
if (groups != null) { if (groups != null) {
i.groups = groups; i.groups = groups;
} }
*/
if (subscription != null) { if (subscription != null) {
i.subscription = subscription; i['subscription'] = subscription;
} }
if (ask != null) { if (ask != null) {
i.ask = ask; i['ask'] = ask;
} }
await _isar.writeTxn(() async { await _db.update(
await _isar.dBRosterItems.put(i); 'RosterItems',
}); i,
where: 'id = ?',
final item = rosterDbToModel(i); whereArgs: [id],
return item; );
return RosterItem.fromJson(i);
} }
} }

View File

@ -0,0 +1,2 @@
int boolToInt(bool b) => b ? 0 : 1;
bool intToBool(int i) => i == 0 ? false : true;

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,6 @@ import 'package:moxxyv2/service/connectivity.dart';
import 'package:moxxyv2/service/connectivity_watcher.dart'; import 'package:moxxyv2/service/connectivity_watcher.dart';
import 'package:moxxyv2/service/conversation.dart'; import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/database/database.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/helpers.dart';
import 'package:moxxyv2/service/httpfiletransfer/helpers.dart'; import 'package:moxxyv2/service/httpfiletransfer/helpers.dart';
import 'package:moxxyv2/service/httpfiletransfer/httpfiletransfer.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/events.dart';
import 'package:moxxyv2/shared/helpers.dart'; import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/shared/migrator.dart'; import 'package:moxxyv2/shared/migrator.dart';
import 'package:moxxyv2/shared/models/media.dart';
import 'package:moxxyv2/shared/models/message.dart'; import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/xmpp/connection.dart'; import 'package:moxxyv2/xmpp/connection.dart';
import 'package:moxxyv2/xmpp/events.dart'; import 'package:moxxyv2/xmpp/events.dart';
@ -365,7 +365,7 @@ class XmppService {
// Create the shared media entries // Create the shared media entries
// Recipient -> [Shared Medium] // Recipient -> [Shared Medium]
final sharedMediaMap = <String, List<DBSharedMedium>>{}; final sharedMediaMap = <String, List<SharedMedium>>{};
final rs = GetIt.I.get<RosterService>(); final rs = GetIt.I.get<RosterService>();
for (final recipient in recipients) { for (final recipient in recipients) {
for (final path in paths) { for (final path in paths) {
@ -378,7 +378,7 @@ class XmppService {
if (sharedMediaMap.containsKey(recipient)) { if (sharedMediaMap.containsKey(recipient)) {
sharedMediaMap[recipient]!.add(medium); sharedMediaMap[recipient]!.add(medium);
} else { } else {
sharedMediaMap[recipient] = List<DBSharedMedium>.from([medium]); sharedMediaMap[recipient] = List<SharedMedium>.from([medium]);
} }
} }
@ -596,7 +596,7 @@ class XmppService {
} }
final msg = await ms.updateMessage( final msg = await ms.updateMessage(
dbMsg.id!, dbMsg.id,
received: true, received: true,
); );
@ -616,7 +616,7 @@ class XmppService {
} }
final msg = await ms.updateMessage( final msg = await ms.updateMessage(
dbMsg.id!, dbMsg.id,
received: dbMsg.received || event.type == 'received' || event.type == 'displayed', received: dbMsg.received || event.type == 'received' || event.type == 'displayed',
displayed: dbMsg.displayed || event.type == 'displayed', displayed: dbMsg.displayed || event.type == 'displayed',
); );
@ -956,7 +956,7 @@ class XmppService {
final ms = GetIt.I.get<MessageService>(); final ms = GetIt.I.get<MessageService>();
final msg = await db.getMessageByXmppId(event.id, jid); final msg = await db.getMessageByXmppId(event.id, jid);
if (msg != null) { if (msg != null) {
await ms.updateMessage(msg.id!, acked: true); await ms.updateMessage(msg.id, acked: true);
} else { } else {
_log.finest('Wanted to mark message as acked but did not find the message to ack'); _log.finest('Wanted to mark message as acked but did not find the message to ack');
} }