moxxy/lib/service/service.dart
Alexander "PapaTutuWawa 4a17ec9812 ui: Refactor the ConversationPage
This refactor was needed to add the line for adding a conversation
to the user's roster or block them.

This also removed one unnecessary event.
2022-03-08 18:18:33 +01:00

450 lines
15 KiB
Dart

import "dart:async";
import "package:moxxyv2/shared/logging.dart";
import "package:moxxyv2/shared/events.dart";
import "package:moxxyv2/shared/commands.dart";
import "package:moxxyv2/shared/helpers.dart";
import "package:moxxyv2/xmpp/connection.dart";
import "package:moxxyv2/xmpp/settings.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/message.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/xeps/xep_0054.dart";
import "package:moxxyv2/xmpp/xeps/xep_0280.dart";
import "package:moxxyv2/xmpp/xeps/xep_0352.dart";
import "package:moxxyv2/xmpp/xeps/xep_0060.dart";
import "package:moxxyv2/xmpp/xeps/xep_0084.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
import "package:moxxyv2/service/managers/roster.dart";
import "package:moxxyv2/service/managers/disco.dart";
import "package:moxxyv2/service/managers/stream.dart";
import "package:moxxyv2/service/database.dart";
import "package:moxxyv2/service/xmpp.dart";
import "package:moxxyv2/service/roster.dart";
import "package:moxxyv2/service/download.dart";
import "package:moxxyv2/service/notifications.dart";
import "package:moxxyv2/service/avatars.dart";
import "package:moxxyv2/service/preferences.dart";
import "package:flutter/material.dart";
import "package:flutter/foundation.dart";
import "package:flutter_background_service/flutter_background_service.dart";
import "package:get_it/get_it.dart";
import "package:isar/isar.dart";
import "package:path_provider/path_provider.dart";
import "package:logging/logging.dart";
import "package:permission_handler/permission_handler.dart";
import "package:moxxyv2/service/db/conversation.dart";
import "package:moxxyv2/service/db/roster.dart";
import "package:moxxyv2/service/db/message.dart";
Future<void> initializeServiceIfNeeded() async {
WidgetsFlutterBinding.ensureInitialized();
final service = FlutterBackgroundService();
if (await service.isServiceRunning()) {
GetIt.I.get<Logger>().info("Stopping background service");
if (kDebugMode) {
//service.stopBackgroundService();
} else {
return;
}
}
GetIt.I.get<Logger>().info("Initializing service");
await initializeService();
}
void Function(BaseIsolateEvent) sendDataMiddleware(FlutterBackgroundService srv) {
return (data) {
final json = data.toJson();
// NOTE: *S*erver to *F*oreground
GetIt.I.get<Logger>().fine("S2F: " + json.toString());
srv.sendData(json);
};
}
Future<void> performPreStart(void Function(BaseIsolateEvent) sendData) async {
final xmpp = GetIt.I.get<XmppService>();
final account = await xmpp.getAccountData();
final settings = await xmpp.getConnectionSettings();
final state = await xmpp.getXmppState();
final preferences = await GetIt.I.get<PreferencesService>().getPreferences();
GetIt.I.get<Logger>().finest("account != null: " + (account != null).toString());
GetIt.I.get<Logger>().finest("settings != null: " + (settings != null).toString());
if (account!= null && settings != null) {
await GetIt.I.get<RosterService>().loadRosterFromDatabase();
// Check some permissions
final storagePerm = await Permission.storage.status;
final List<int> permissions = List.empty(growable: true);
if (storagePerm.isDenied /*&& !state.askedStoragePermission*/) {
permissions.add(Permission.storage.value);
await xmpp.modifyXmppState((state) => state.copyWith(
askedStoragePermission: true
));
}
sendData(PreStartResultEvent(
state: "logged_in",
jid: account.jid,
displayName: account.displayName,
avatarUrl: account.avatarUrl,
debugEnabled: state.debugEnabled,
permissionsToRequest: permissions,
preferences: preferences
));
} else {
sendData(PreStartResultEvent(
state: "not_logged_in",
debugEnabled: state.debugEnabled,
permissionsToRequest: List<int>.empty(),
preferences: preferences
));
}
}
Future<Isar> openDatabase() async {
final dir = await getApplicationSupportDirectory();
return await Isar.open(
schemas: [
DBConversationSchema,
DBRosterItemSchema,
DBMessageSchema
],
directory: dir.path
);
}
void setupLogging() {
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.onRecord.listen((record) {
final logMessage = "[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}";
if (GetIt.I.isRegistered<UDPLogger>()) {
final udp = GetIt.I.get<UDPLogger>();
if (udp.isEnabled()) {
udp.sendLog(logMessage, record.time.millisecondsSinceEpoch, record.level.name);
}
}
if (kDebugMode) {
// ignore: avoid_print
print(logMessage);
}
});
}
Future<void> initUDPLogger() async {
final state = await GetIt.I.get<XmppService>().getXmppState();
if (state.debugEnabled) {
GetIt.I.get<Logger>().finest("UDPLogger created");
final port = state.debugPort;
final ip = state.debugIp;
final passphrase = state.debugPassphrase;
if (port != 0 && ip.isNotEmpty && passphrase.isNotEmpty) {
GetIt.I.get<UDPLogger>().init(passphrase, ip, port);
}
} else {
GetIt.I.get<UDPLogger>().setEnabled(false);
}
}
void onStart() {
WidgetsFlutterBinding.ensureInitialized();
setupLogging();
GetIt.I.registerSingleton<Logger>(Logger("XmppService"));
final service = FlutterBackgroundService();
service.onDataReceived.listen(handleEvent);
service.setNotificationInfo(title: "Moxxy", content: "Connecting...");
GetIt.I.get<Logger>().finest("Running...");
GetIt.I.registerSingleton<PreferencesService>(PreferencesService());
GetIt.I.registerSingleton<NotificationsService>(NotificationsService());
(() async {
await GetIt.I.get<NotificationsService>().init();
final middleware = sendDataMiddleware(service);
// Register singletons
GetIt.I.registerSingleton<UDPLogger>(UDPLogger());
final db = DatabaseService(isar: await openDatabase(), sendData: middleware);
GetIt.I.registerSingleton<DatabaseService>(db);
final xmpp = XmppService(sendData: (data) {
if (data is ConnectionStateEvent) {
if (data.state == XmppConnectionState.connected.toString().split(".")[1]) {
FlutterBackgroundService().setNotificationInfo(title: "Moxxy", content: "Ready to receive messages");
} else if (data.state == XmppConnectionState.connecting.toString().split(".")[1]) {
FlutterBackgroundService().setNotificationInfo(title: "Moxxy", content: "Connecting...");
} else {
FlutterBackgroundService().setNotificationInfo(title: "Moxxy", content: "Disconnected");
}
}
middleware(data);
});
GetIt.I.registerSingleton<XmppService>(xmpp);
GetIt.I.registerSingleton<DownloadService>(DownloadService(middleware));
GetIt.I.registerSingleton<AvatarService>(AvatarService(middleware));
// Init the UDPLogger
await initUDPLogger();
GetIt.I.registerSingleton<RosterService>(RosterService(sendData: middleware));
final connection = XmppConnection();
connection.registerManager(MoxxyStreamManagementManager());
connection.registerManager(MoxxyDiscoManager());
connection.registerManager(MessageManager());
connection.registerManager(MoxxyRosterManger());
connection.registerManager(PresenceManager());
connection.registerManager(CSIManager());
connection.registerManager(DiscoCacheManager());
connection.registerManager(CarbonsManager());
connection.registerManager(PubSubManager());
connection.registerManager(vCardManager());
connection.registerManager(UserAvatarManager());
GetIt.I.registerSingleton<XmppConnection>(connection);
final account = await xmpp.getAccountData();
final settings = await xmpp.getConnectionSettings();
if (account!= null && settings != null) {
xmpp.connect(settings, false);
}
})();
}
Future<FlutterBackgroundService> initializeService() async {
final service = FlutterBackgroundService();
await service.configure(
// TODO: iOS
iosConfiguration: IosConfiguration(
autoStart: true,
onBackground: () {},
onForeground: () {}
),
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true
)
);
return service;
}
void handleEvent(Map<String, dynamic>? data) {
// NOTE: *F*oreground to *S*ervice
GetIt.I.get<Logger>().fine("F2S: " + data.toString());
switch (data!["type"]) {
case loadConversationsType: {
GetIt.I.get<DatabaseService>().loadConversations();
}
break;
case performLoginType: {
final command = PerformLoginAction.fromJson(data);
GetIt.I.get<Logger>().fine("Performing login");
GetIt.I.get<XmppService>().connect(ConnectionSettings(
jid: JID.fromString(command.jid),
password: command.password,
useDirectTLS: command.useDirectTLS,
allowPlainAuth: command.allowPlainAuth
), true);
}
break;
case loadMessagesForJidActionType: {
final command = LoadMessagesForJidAction.fromJson(data);
GetIt.I.get<DatabaseService>().loadMessagesForJid(command.jid);
}
break;
case setCurrentlyOpenChatType: {
final command = SetCurrentlyOpenChatAction.fromJson(data);
GetIt.I.get<XmppService>().setCurrentlyOpenedChatJid(command.jid);
}
break;
case addToRosterType: {
final command = AddToRosterAction.fromJson(data);
final String jid = command.jid;
(() async {
final roster = GetIt.I.get<RosterService>();
if (await roster.isInRoster(jid)) {
// TODO: Use a global middleware
FlutterBackgroundService().sendData(
AddToRosterResultEvent(
result: "error",
msg: "Already in contact list"
).toJson()
);
return;
}
roster.addToRosterWrapper("", jid, jid.split("@")[0]);
FlutterBackgroundService().sendData(
AddToRosterResultEvent(
result: "success",
jid: jid
).toJson()
);
final db = GetIt.I.get<DatabaseService>();
final conversation = await db.getConversationByJid(jid);
if (conversation != null) {
final c = await db.updateConversation(id: conversation.id, open: true);
FlutterBackgroundService().sendData(
ConversationUpdatedEvent(conversation: c).toJson()
);
} else {
final c = await db.addConversationFromData(
jid.split("@")[0],
"",
"",
jid,
0,
-1,
[],
true
);
FlutterBackgroundService().sendData(
ConversationCreatedEvent(conversation: c).toJson()
);
}
// Try to figure out an avatar
await GetIt.I.get<AvatarService>().subscribeJid(jid);
GetIt.I.get<AvatarService>().fetchAndUpdateAvatarForJid(jid);
})();
}
break;
case removeRosterItemActionType: {
final command = RemoveRosterItemAction.fromJson(data);
GetIt.I.get<RosterService>().removeFromRosterWrapper(command.jid);
GetIt.I.get<AvatarService>().unsubscribeJid(command.jid.toString());
}
break;
case sendMessageActionType: {
final command = SendMessageAction.fromJson(data);
GetIt.I.get<XmppService>().sendMessage(
body: command.body,
jid: command.jid,
quotedMessage: command.quotedMessage
);
}
break;
case setCSIStateType: {
final command = SetCSIStateAction.fromJson(data);
final csi = GetIt.I.get<XmppConnection>().getManagerById(csiManager);
if (csi == null) {
return;
}
if (command.state == "foreground") {
csi.setActive();
} else {
csi.setInactive();
}
}
break;
case performPrestartActionType: {
// TODO: This assumes that we are ready if we receive this event
performPreStart((event) {
// TODO: Maybe register the middleware via GetIt
final data = event.toJson();
GetIt.I.get<Logger>().fine("S2F: " + data.toString());
FlutterBackgroundService().sendData(data);
});
}
break;
case debugSetEnabledActionType: {
final command = DebugSetEnabledAction.fromJson(data);
(() async {
await GetIt.I.get<XmppService>().modifyXmppState((state) => state.copyWith(
debugEnabled: command.enabled
));
initUDPLogger();
})();
}
break;
case debugSetIpActionType: {
final command = DebugSetIpAction.fromJson(data);
(() async {
await GetIt.I.get<XmppService>().modifyXmppState((state) => state.copyWith(
debugIp: command.ip
));
initUDPLogger();
})();
}
break;
case debugSetPortActionType: {
final command = DebugSetPortAction.fromJson(data);
(() async {
await GetIt.I.get<XmppService>().modifyXmppState((state) => state.copyWith(
debugPort: command.port
));
initUDPLogger();
})();
}
break;
case debugSetPassphraseActionType: {
final command = DebugSetPassphraseAction.fromJson(data);
(() async {
await GetIt.I.get<XmppService>().modifyXmppState((state) => state.copyWith(
debugPassphrase: command.passphrase
));
initUDPLogger();
})();
}
break;
case performDownloadActionType: {
final command = PerformDownloadAction.fromJson(data);
sendDataMiddleware(FlutterBackgroundService())(
MessageUpdatedEvent(message: command.message.copyWith(isDownloading: true))
);
(() async {
final download = GetIt.I.get<DownloadService>();
final metadata = await download.peekFile(command.message.srcUrl!);
// TODO: Maybe deduplicate with the code in the xmpp service
// NOTE: This either works by returing "jpg" for ".../hallo.jpg" or fails
// for ".../aaaaaaaaa", in which case we would've failed anyways.
final ext = command.message.srcUrl!.split(".").last;
final mimeGuess = metadata.mime ?? guessMimeTypeFromExtension(ext);
await download.downloadFile(command.message.srcUrl!, command.message.id, command.message.conversationJid, mimeGuess);
})();
}
break;
case setPreferencesCommandType: {
final command = SetPreferencesCommand.fromJson(data);
GetIt.I.get<PreferencesService>().modifyPreferences((prefs) => command.preferences);
}
break;
case stopActionType: {
FlutterBackgroundService().stopBackgroundService();
}
break;
}
}