diff --git a/bin/running_on_dart.dart b/bin/running_on_dart.dart index 07e52dd..9839375 100644 --- a/bin/running_on_dart.dart +++ b/bin/running_on_dart.dart @@ -55,12 +55,9 @@ void main() async { ], )); - final db = DatabaseService(); - await db.awaitReady(); - Injector.appInstance ..registerSingleton(() => client) - ..registerSingleton(() => db) + ..registerSingleton(() => DatabaseService()) ..registerSingleton(() => FeatureSettingsRepository()) ..registerSingleton(() => JellyfinConfigRepository()) ..registerSingleton(() => ReminderRepository()) @@ -74,4 +71,14 @@ void main() async { ..registerSingleton(() => TagModule()) ..registerSingleton(() => DocsModule()) ..registerSingleton(() => JellyfinModule()); + + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); + await Injector.appInstance.get().init(); } diff --git a/lib/src/commands/jellyfin.dart b/lib/src/commands/jellyfin.dart index 47a401b..7ff3e67 100644 --- a/lib/src/commands/jellyfin.dart +++ b/lib/src/commands/jellyfin.dart @@ -8,8 +8,8 @@ import 'package:running_on_dart/running_on_dart.dart'; import 'package:running_on_dart/src/checks.dart'; import 'package:running_on_dart/src/models/jellyfin_config.dart'; import 'package:running_on_dart/src/repository/jellyfin_config.dart'; -import 'package:running_on_dart/src/util/custom_task.dart'; import 'package:running_on_dart/src/util/jellyfin.dart'; +import 'package:running_on_dart/src/util/pipelines.dart'; import 'package:tentacle/tentacle.dart'; final taskProgressFormat = NumberFormat("0.00"); @@ -47,25 +47,26 @@ final jellyfin = ChatGroup( return SelectMenuOptionBuilder(label: label, value: taskInfo.id!, description: description); }, authorOnly: true); - client.startTask(selectMenuResult.id!); + Pipeline.fromUpdateContext( + messageSupplier: (messageBuilder) => context.interaction.updateOriginalResponse(messageBuilder), + tasks: [ + Task( + runCallback: () => client.startTask(selectMenuResult.id!), + updateCallback: () async { + final scheduledTask = (await client.getScheduledTasks()) + .firstWhereOrNull((taskInfo) => taskInfo.id == selectMenuResult.id); + if (scheduledTask == null || scheduledTask.state == TaskState.idle) { + return (true, null); + } - CustomTask( - targetMessageCallback: () => context.interaction.updateOriginalResponse( - MessageUpdateBuilder(content: "Running `${selectMenuResult.name!}`", components: [])), - updateCallback: (builder) async { - final scheduledTask = - (await client.getScheduledTasks()).firstWhereOrNull((taskInfo) => taskInfo.id == selectMenuResult.id); - if (scheduledTask == null || scheduledTask.state == TaskState.idle) { - builder.content = "Running `${selectMenuResult.name!}` - Done!"; - return true; - } - - builder.content = - "Running `${scheduledTask.name!}` - ${taskProgressFormat.format(scheduledTask.currentProgressPercentage!)}%"; - return false; - }, + return ( + false, + "Running `${scheduledTask.name!}` - ${taskProgressFormat.format(scheduledTask.currentProgressPercentage!)}%" + ); + }), + ], updateInterval: Duration(seconds: 2), - ); + ).execute(); }), ), ]), diff --git a/lib/src/modules/bot_start_duration.dart b/lib/src/modules/bot_start_duration.dart index fde782a..23f6b7e 100644 --- a/lib/src/modules/bot_start_duration.dart +++ b/lib/src/modules/bot_start_duration.dart @@ -1,3 +1,10 @@ -class BotStartDuration { - final DateTime startDate = DateTime.now(); +import 'package:running_on_dart/src/util/util.dart'; + +class BotStartDuration implements RequiresInitialization { + late final DateTime startDate; + + @override + Future init() async { + startDate = DateTime.now(); + } } diff --git a/lib/src/modules/docs.dart b/lib/src/modules/docs.dart index 789ca89..e00b936 100644 --- a/lib/src/modules/docs.dart +++ b/lib/src/modules/docs.dart @@ -3,12 +3,14 @@ import 'dart:async'; import 'package:fuzzy/fuzzy.dart'; import 'package:running_on_dart/running_on_dart.dart'; import 'package:running_on_dart/src/models/docs.dart'; +import 'package:running_on_dart/src/util/util.dart'; -class DocsModule { +class DocsModule implements RequiresInitialization { final Map _cache = {}; DateTime? lastUpdate; - DocsModule() { + @override + Future init() async { for (final package in docsPackages) { _cache[package] = PackageDocs(packageName: package); } diff --git a/lib/src/modules/jellyfin.dart b/lib/src/modules/jellyfin.dart index c75e7c0..d33868d 100644 --- a/lib/src/modules/jellyfin.dart +++ b/lib/src/modules/jellyfin.dart @@ -2,6 +2,7 @@ import 'package:injector/injector.dart'; import 'package:nyxx/nyxx.dart'; import 'package:running_on_dart/src/models/jellyfin_config.dart'; import 'package:running_on_dart/src/repository/jellyfin_config.dart'; +import 'package:running_on_dart/src/util/util.dart'; import 'package:tentacle/tentacle.dart'; import 'package:tentacle/src/auth/auth.dart' show AuthInterceptor; import 'package:dio/dio.dart' show RequestInterceptorHandler, RequestOptions; @@ -103,16 +104,18 @@ class JellyfinClientWrapper { Uri getJellyfinItemUrl(String itemId) => Uri.parse("$basePath/#/details?id=$itemId"); } -class JellyfinModule { +class JellyfinModule implements RequiresInitialization { final Map _jellyfinClients = {}; final Map> _allowedUserRegistrations = {}; final JellyfinConfigRepository _jellyfinConfigRepository = Injector.appInstance.get(); - JellyfinModule() { - _jellyfinConfigRepository - .getDefaultConfigs() - .then((defaultConfigs) => defaultConfigs.forEach((config) => _createClientConfig(config))); + @override + Future init() async { + final defaultConfigs = await _jellyfinConfigRepository.getDefaultConfigs(); + for (final config in defaultConfigs) { + _createClientConfig(config); + } } Future deleteJellyfinConfig(JellyfinConfig config) async { diff --git a/lib/src/modules/join_logs.dart b/lib/src/modules/join_logs.dart index b88574a..5054cfc 100644 --- a/lib/src/modules/join_logs.dart +++ b/lib/src/modules/join_logs.dart @@ -5,15 +5,17 @@ import 'package:running_on_dart/src/models/feature_settings.dart'; import 'package:running_on_dart/src/repository/feature_settings.dart'; import 'package:running_on_dart/src/services/feature_settings.dart'; import 'package:running_on_dart/src/settings.dart'; +import 'package:running_on_dart/src/util/util.dart'; -class JoinLogsModule { +class JoinLogsModule implements RequiresInitialization { final NyxxGateway _client = Injector.appInstance.get(); final FeatureSettingsRepository _featureSettingsRepository = Injector.appInstance.get(); final FeatureSettingsService _featureSettingsService = Injector.appInstance.get(); final Logger _logger = Logger('ROD.JoinLogs'); - JoinLogsModule() { + @override + Future init() async { _client.onGuildMemberAdd.listen(_handle); } diff --git a/lib/src/modules/mod_log.dart b/lib/src/modules/mod_log.dart index 933ea29..9d8fcf1 100644 --- a/lib/src/modules/mod_log.dart +++ b/lib/src/modules/mod_log.dart @@ -5,8 +5,9 @@ import 'package:running_on_dart/running_on_dart.dart'; import 'package:running_on_dart/src/models/feature_settings.dart'; import 'package:running_on_dart/src/repository/feature_settings.dart'; import 'package:running_on_dart/src/services/feature_settings.dart'; +import 'package:running_on_dart/src/util/util.dart'; -class ModLogsModule { +class ModLogsModule implements RequiresInitialization { final NyxxGateway _client = Injector.appInstance.get(); final FeatureSettingsRepository _featureSettingsRepository = Injector.appInstance.get(); final FeatureSettingsService _featureSettingsService = Injector.appInstance.get(); @@ -18,7 +19,8 @@ class ModLogsModule { AuditLogEvent.memberKick, ]; - ModLogsModule() { + @override + Future init() async { _client.onGuildAuditLogCreate.listen(_handleAuditLogAdd); } diff --git a/lib/src/modules/poop_name.dart b/lib/src/modules/poop_name.dart index 9223b43..38113d2 100644 --- a/lib/src/modules/poop_name.dart +++ b/lib/src/modules/poop_name.dart @@ -3,15 +3,17 @@ import 'package:nyxx/nyxx.dart'; import 'package:running_on_dart/src/models/feature_settings.dart'; import 'package:running_on_dart/src/services/feature_settings.dart'; import 'package:running_on_dart/src/settings.dart'; +import 'package:running_on_dart/src/util/util.dart'; const poopEmoji = "💩"; final poopCharacters = ['!', '#', '@', '^', '%', '&', '-', '*', '.' '+', '\'']; final poopRegexp = RegExp("[${poopCharacters.join()}]"); -class PoopNameModule { +class PoopNameModule implements RequiresInitialization { final NyxxGateway _client = Injector.appInstance.get(); - PoopNameModule() { + @override + Future init() async { _client.onGuildMemberAdd.listen((event) => _handle(event.member)); _client.onGuildMemberUpdate.listen((event) => _handle(event.member)); } diff --git a/lib/src/modules/reminder.dart b/lib/src/modules/reminder.dart index add95f6..a89ccbf 100644 --- a/lib/src/modules/reminder.dart +++ b/lib/src/modules/reminder.dart @@ -6,6 +6,7 @@ import 'package:nyxx/nyxx.dart'; import 'package:nyxx_extensions/nyxx_extensions.dart'; import 'package:running_on_dart/src/models/reminder.dart'; import 'package:running_on_dart/src/repository/reminder.dart'; +import 'package:running_on_dart/src/util/util.dart'; class ReminderModuleComponentId { static String identifier = 'ReminderModuleComponentId'; @@ -33,17 +34,17 @@ class ReminderModuleComponentId { String toString() => "$identifier/$reminderId/$userId/${duration.inMinutes}"; } -class ReminderModule { +class ReminderModule implements RequiresInitialization { final List reminders = []; final Logger _logger = Logger('ROD.ReminderModule'); final NyxxGateway _client = Injector.appInstance.get(); final ReminderRepository _reminderRepository = Injector.appInstance.get(); - ReminderModule() { - _reminderRepository.fetchReminders().then((reminders) => this.reminders.addAll(reminders)); - - _processCurrent(); + @override + Future init() async { + reminders.addAll(await _reminderRepository.fetchReminders()); + await _processCurrent(); _client.onMessageComponentInteraction .where((event) => event.interaction.data.type == MessageComponentType.button) diff --git a/lib/src/modules/tag.dart b/lib/src/modules/tag.dart index b9954fd..98daf64 100644 --- a/lib/src/modules/tag.dart +++ b/lib/src/modules/tag.dart @@ -4,14 +4,16 @@ import 'package:nyxx/nyxx.dart'; import 'package:running_on_dart/src/models/tag.dart'; import 'package:running_on_dart/src/repository/tag.dart'; import 'package:running_on_dart/src/settings.dart'; +import 'package:running_on_dart/src/util/util.dart'; -class TagModule { +class TagModule implements RequiresInitialization { final List tags = []; final List usedEvents = []; final _tagRepository = Injector.appInstance.get(); - TagModule() { + @override + Future init() async { _tagRepository.fetchAllActiveTags().then((tags) => this.tags.addAll(tags)); _tagRepository.fetchTagUsage().then((events) => usedEvents.addAll(events)); } diff --git a/lib/src/services/db.dart b/lib/src/services/db.dart index e7ec826..7c28335 100644 --- a/lib/src/services/db.dart +++ b/lib/src/services/db.dart @@ -6,6 +6,7 @@ import 'package:migent/migent.dart'; import 'package:postgres/postgres.dart'; import 'package:running_on_dart/src/settings.dart'; +import 'package:running_on_dart/src/util/util.dart'; /// The user to use when connecting to the database. String user = getEnv('POSTGRES_USER'); @@ -23,17 +24,13 @@ String host = getEnv('DB_HOST', 'db'); /// The port to connect to the database on. int port = int.parse(getEnv('DB_PORT', '5432')); -class DatabaseService { - /// The connection to the database. +class DatabaseService implements RequiresInitialization { late PostgreSQLConnection _connection; - final Logger _logger = Logger('ROD.Database'); - final Completer _readyCompleter = Completer(); - late final Future _ready = _readyCompleter.future; - - DatabaseService() { - _connect(); + @override + Future init() async { + await _connect(); } /// Connect to the database and ensure the schema is up to date. @@ -149,11 +146,7 @@ class DatabaseService { await migrator.runMigrations(); _logger.info('Connected to database'); - - _readyCompleter.complete(); } - Future awaitReady() async => await _ready; - PostgreSQLConnection getConnection() => _connection; } diff --git a/lib/src/util/jellyfin.dart b/lib/src/util/jellyfin.dart index c23dd12..a7d7c50 100644 --- a/lib/src/util/jellyfin.dart +++ b/lib/src/util/jellyfin.dart @@ -3,6 +3,7 @@ import 'package:intl/intl.dart'; import 'package:nyxx/nyxx.dart'; import 'package:nyxx_extensions/nyxx_extensions.dart'; import 'package:running_on_dart/src/modules/jellyfin.dart'; +import 'package:running_on_dart/src/util/util.dart'; import 'package:tentacle/tentacle.dart'; final episodeSeriesNumberFormat = NumberFormat("00"); @@ -11,10 +12,6 @@ final itemCriticRatingNumberFormat = NumberFormat("00"); Duration parseDurationFromTicks(int ticks) => Duration(microseconds: ticks ~/ 10); -extension DurationFromTicks on Duration { - String formatShort() => toString().split('.').first.padLeft(8, "0"); -} - String formatProgress(int currentPositionTicks, int totalTicks) { final progressPercentage = currentPositionTicks / totalTicks * 100; diff --git a/lib/src/util/pipelines.dart b/lib/src/util/pipelines.dart new file mode 100644 index 0000000..af5ee0f --- /dev/null +++ b/lib/src/util/pipelines.dart @@ -0,0 +1,99 @@ +import 'dart:async'; + +import 'package:nyxx/nyxx.dart'; +import 'package:running_on_dart/src/util/util.dart'; + +typedef TargetMessageSupplier = Future Function(MessageBuilder); +typedef TargetUpdateMessageSupplier = Future Function(MessageUpdateBuilder); +typedef MessageSupplier = Future Function(); + +typedef UpdateCallback = Future<(bool, String?)> Function(); +typedef RunCallback = Future Function(); + +EmbedBuilder getInitialEmbed(int taskAmount) => + EmbedBuilder(title: 'Task 1 of $taskAmount', description: 'Starting...'); + +class Task { + final UpdateCallback updateCallback; + final RunCallback runCallback; + + const Task({required this.runCallback, required this.updateCallback}); +} + +class InternalTask { + final UpdateCallback updateCallback; + final Duration updateInterval; + final Message targetMessage; + + const InternalTask({required this.targetMessage, required this.updateCallback, required this.updateInterval}); + + Future execute(EmbedBuilder embed) async { + final completer = Completer(); + + Timer.periodic(updateInterval, (timer) async { + final (finished, currentStatus) = await updateCallback(); + + embed.description = currentStatus.toString(); + await targetMessage.update(MessageUpdateBuilder(embeds: [embed])); + + if (finished) { + completer.complete(); + timer.cancel(); + } + }); + + return completer.future; + } +} + +class Pipeline { + final List tasks; + final MessageSupplier messageSupplier; + final Duration updateInterval; + + late EmbedBuilder embed; + + Pipeline({required this.messageSupplier, required this.tasks, required this.updateInterval, required this.embed}); + + factory Pipeline.fromCreateContext( + {required TargetMessageSupplier messageSupplier, required List tasks, required Duration updateInterval}) { + final embed = getInitialEmbed(tasks.length); + + return Pipeline( + messageSupplier: () => messageSupplier(MessageBuilder(embeds: [embed], components: [], content: null)), + tasks: tasks, + updateInterval: updateInterval, + embed: embed); + } + + factory Pipeline.fromUpdateContext( + {required TargetUpdateMessageSupplier messageSupplier, + required List tasks, + required Duration updateInterval}) { + final embed = getInitialEmbed(tasks.length); + + return Pipeline( + messageSupplier: () => messageSupplier(MessageUpdateBuilder(embeds: [embed], components: [], content: null)), + tasks: tasks, + updateInterval: updateInterval, + embed: embed, + ); + } + + Future execute() async { + final message = await messageSupplier(); + + final timer = Stopwatch()..start(); + for (final task in tasks) { + final internalTask = + InternalTask(targetMessage: message, updateCallback: task.updateCallback, updateInterval: updateInterval); + + task.runCallback(); + await internalTask.execute(embed); + } + + embed.description = 'Done! Took ${timer.elapsed.formatShort()}'; + + message.update(MessageUpdateBuilder(embeds: [embed])); + } +} diff --git a/lib/src/util/util.dart b/lib/src/util/util.dart index 390470e..a9e0c8f 100644 --- a/lib/src/util/util.dart +++ b/lib/src/util/util.dart @@ -16,3 +16,11 @@ String getCurrentMemoryString() { } String getDartPlatform() => Platform.version.split('(').first; + +extension DurationFromTicks on Duration { + String formatShort() => toString().split('.').first.padLeft(8, "0"); +} + +abstract class RequiresInitialization { + Future init(); +}