Skip to content

Commit

Permalink
Improve plugin interface (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
abitofevrything authored Sep 16, 2023
1 parent cca3200 commit e2cd7b4
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 33 deletions.
10 changes: 8 additions & 2 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ import 'package:oauth2/oauth2.dart';

/// A helper function to nest and execute calls to plugin connect methods.
Future<T> _doConnect<T extends Nyxx>(ApiOptions apiOptions, ClientOptions clientOptions, Future<T> Function() connect, List<NyxxPlugin> plugins) {
connect = plugins.fold(connect, (previousConnect, plugin) => () => plugin.connect(apiOptions, clientOptions, previousConnect));
connect = plugins.fold(
connect,
(previousConnect, plugin) => () async => (await plugin.doConnect(apiOptions, clientOptions, previousConnect)) as T,
);
return connect();
}

/// A helper function to nest and execute calls to plugin close methods.
Future<void> _doClose(Nyxx client, Future<void> Function() close, List<NyxxPlugin> plugins) {
close = plugins.fold(close, (previousClose, plugin) => () => plugin.close(client, previousClose));
close = plugins.fold(
close,
(previousClose, plugin) => () => plugin.doClose(client, previousClose),
);
return close();
}

Expand Down
18 changes: 5 additions & 13 deletions lib/src/plugin/cli_integration.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:nyxx/src/api_options.dart';
import 'package:nyxx/src/client.dart';
import 'package:nyxx/src/client_options.dart';
import 'package:nyxx/src/plugin/plugin.dart';

/// A global instance of the [CliIntegration] plugin.
Expand Down Expand Up @@ -44,26 +42,20 @@ class CliIntegration extends NyxxPlugin {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
_subscriptions = null;
}

@override
Future<ClientType> connect<ClientType extends Nyxx>(ApiOptions apiOptions, ClientOptions clientOptions, Future<ClientType> Function() connect) async {
void afterConnect(Nyxx client) {
_ensureListening();

try {
final client = await connect();
_watchedClients.add(client);
client.logger.info('Listening for SIGINT or SIGTERM to safely close');
return client;
} finally {
_removeListenersIfNeeded();
}
_watchedClients.add(client);
client.logger.info('Listening for SIGINT or SIGTERM to safely close');
}

@override
Future<void> close(Nyxx client, Future<void> Function() close) async {
void beforeClose(Nyxx client) {
_watchedClients.remove(client);
_removeListenersIfNeeded();
await close();
}
}
11 changes: 2 additions & 9 deletions lib/src/plugin/ignore_exceptions.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import 'dart:isolate';

import 'package:logging/logging.dart';
import 'package:nyxx/src/api_options.dart';
import 'package:nyxx/src/client.dart';
import 'package:nyxx/src/client_options.dart';
import 'package:nyxx/src/plugin/plugin.dart';

/// A global instance of the [IgnoreExceptions] plugin.
Expand Down Expand Up @@ -53,18 +51,13 @@ class IgnoreExceptions extends NyxxPlugin {
}

@override
Future<ClientType> connect<ClientType extends Nyxx>(ApiOptions apiOptions, ClientOptions clientOptions, Future<ClientType> Function() connect) async {
final client = await connect();

void afterConnect(Nyxx client) {
_clients++;
_listenIfNeeded();

return client;
}

@override
Future<void> close(Nyxx client, Future<void> Function() close) async {
await close();
void afterClose() {
_clients--;
_stopListeningIfNeeded();
}
Expand Down
8 changes: 4 additions & 4 deletions lib/src/plugin/logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,19 @@ class Logging extends NyxxPlugin {
}

@override
Future<ClientType> connect<ClientType extends Nyxx>(ApiOptions apiOptions, ClientOptions clientOptions, Future<ClientType> Function() connect) async {
void beforeConnect(ApiOptions apiOptions, ClientOptions clientOptions) {
if (apiOptions is RestApiOptions) {
_tokens.add(apiOptions.token);
}

_clients++;
_listenIfNeeded();
return await connect();
}

@override
Future<void> close(Nyxx client, Future<void> Function() close) async {
await close();
Future<void> doClose(Nyxx client, Future<void> Function() close) async {
await super.doClose(client, close);

_clients--;
_stopListeningIfNeeded();

Expand Down
76 changes: 71 additions & 5 deletions lib/src/plugin/plugin.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,85 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:nyxx/src/api_options.dart';
import 'package:nyxx/src/client.dart';
import 'package:nyxx/src/client_options.dart';

/// Provides access to the connection and closing process for implementing plugins.
abstract class NyxxPlugin {
abstract class NyxxPlugin<ClientType extends Nyxx> {
/// The name of this plugin.
String get name;

late final Expando<NyxxPluginState<ClientType, NyxxPlugin<ClientType>>> _states = Expando('$name plugin states');

/// Perform the connection operation.
///
/// The function passed as an argument should be called to obtain the underlying client.
Future<ClientType> connect<ClientType extends Nyxx>(ApiOptions apiOptions, ClientOptions clientOptions, Future<ClientType> Function() connect) => connect();
/// People overriding this method should call it to obtain the client instance.
@mustCallSuper
Future<ClientType> doConnect(ApiOptions apiOptions, ClientOptions clientOptions, Future<ClientType> Function() connect) async {
final state = await createState();
await state.beforeConnect(apiOptions, clientOptions);

final client = await connect();
_states[client] = state;

await state.afterConnect(client);
return client;
}

/// Perform the close operation.
///
/// The function passed as an argument should be called to close the underlying client.
Future<void> close(Nyxx client, Future<void> Function() close) => close();
/// People overriding this method should call it to obtain the client instance.
Future<void> doClose(ClientType client, Future<void> Function() close) async {
final state = _states[client];
await state?.beforeClose(client);

await close();
_states[client] = null;

await state?.afterClose();
}

/// Called to create the state for this plugin.
///
/// Each plugin creates a state for each client is attached to. States can contain mutable fields that can be updated at any time without affecting other
/// instances of the plugin attached to other clients.
FutureOr<NyxxPluginState<ClientType, NyxxPlugin<ClientType>>> createState() => NyxxPluginState(this);

/// Called before each client this plugin is added to connects.
FutureOr<void> beforeConnect(ApiOptions apiOptions, ClientOptions clientOptions) {}

/// Called after each client this plugin is added to connects.
FutureOr<void> afterConnect(ClientType client) {}

/// Called before each client this plugin is added to closes.
FutureOr<void> beforeClose(ClientType client) {}

/// Called after each client this plugin is added to closes.
FutureOr<void> afterClose() {}
}

/// Holds the state of a plugin added to a client.
class NyxxPluginState<ClientType extends Nyxx, PluginType extends NyxxPlugin<ClientType>> {
/// The plugin this state belongs to.
final PluginType plugin;

/// Create a new plugin state.
NyxxPluginState(this.plugin);

/// Called before each client this plugin is added to connects.
@mustCallSuper
FutureOr<void> beforeConnect(ApiOptions apiOptions, ClientOptions clientOptions) => plugin.beforeConnect(apiOptions, clientOptions);

/// Called after each client this plugin is added to connects.
@mustCallSuper
FutureOr<void> afterConnect(ClientType client) => plugin.afterConnect(client);

/// Called before each client this plugin is added to closes.
@mustCallSuper
FutureOr<void> beforeClose(ClientType client) => plugin.beforeClose(client);

/// Called after each client this plugin is added to closes.
@mustCallSuper
FutureOr<void> afterClose() => plugin.afterClose();
}

0 comments on commit e2cd7b4

Please sign in to comment.