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: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 {
|
||||||
|
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,
|
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
|
||||||
)''',
|
)''',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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/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');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user