Skip to content

Commit

Permalink
Refactor SpinifyCommandMixin error handling and connection logic
Browse files Browse the repository at this point in the history
  • Loading branch information
PlugFox committed May 13, 2024
1 parent 141d097 commit 2de2f82
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 55 deletions.
122 changes: 116 additions & 6 deletions lib/src/model/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,121 @@ typedef SpinifyTokenCallback = FutureOr<SpinifyToken?> Function();
/// {@category Entity}
typedef SpinifyConnectionPayloadCallback = FutureOr<List<int>?> Function();

/// Log level for logger
extension type const SpinifyLogLevel._(int level) {
/// Log level: debug
@literal
const SpinifyLogLevel.debug() : level = 0;

/// Log level: transport
@literal
const SpinifyLogLevel.transport() : level = 1;

/// Log level: config
@literal
const SpinifyLogLevel.config() : level = 2;

/// Log level: info
@literal
const SpinifyLogLevel.info() : level = 3;

/// Log level: warning
@literal
const SpinifyLogLevel.warning() : level = 4;

/// Log level: error
@literal
const SpinifyLogLevel.error() : level = 5;

/// Log level: critical
@literal
const SpinifyLogLevel.critical() : level = 6;

/// Pattern matching on log level
T map<T>({
required T Function() debug,
required T Function() transport,
required T Function() config,
required T Function() info,
required T Function() warning,
required T Function() error,
required T Function() critical,
}) =>
switch (level) {
0 => debug(),
1 => transport(),
2 => config(),
3 => info(),
4 => warning(),
5 => error(),
6 => critical(),
_ => throw AssertionError('Unknown log level: $level'),
};

/// Pattern matching on log level
T maybeMap<T>({
required T Function() orElse,
T Function()? debug,
T Function()? transport,
T Function()? config,
T Function()? info,
T Function()? warning,
T Function()? error,
T Function()? critical,
}) =>
map<T>(
debug: debug ?? orElse,
transport: transport ?? orElse,
config: config ?? orElse,
info: info ?? orElse,
warning: warning ?? orElse,
error: error ?? orElse,
critical: critical ?? orElse,
);

/// Pattern matching on log level
T? mapOrNull<T>({
T Function()? debug,
T Function()? transport,
T Function()? config,
T Function()? info,
T Function()? warning,
T Function()? error,
T Function()? critical,
}) =>
maybeMap<T?>(
orElse: () => null,
debug: debug,
transport: transport,
config: config,
info: info,
warning: warning,
error: error,
critical: critical,
);
}

/// Logger function to use for logging.
/// If not specified, the logger will be disabled.
/// The logger function is called with the following arguments:
/// - [level] - the log verbose level 0..6
/// * 0 - debug
/// * 1 - transport
/// * 2 - config
/// * 3 - info
/// * 4 - warning
/// * 5 - error
/// * 6 - critical
/// - [event] - the log event, unique type of log event
/// - [message] - the log message
/// - [context] - the log context data
typedef SpinifyLogger = void Function(
SpinifyLogLevel level,
String event,
String message,
Map<String, Object?> context,
);

/// {@template spinify_config}
/// Spinify client common options.
///
Expand Down Expand Up @@ -127,12 +242,7 @@ final class SpinifyConfig {
/// - [event] - the log event, unique type of log event
/// - [message] - the log message
/// - [context] - the log context data
final void Function(
int level,
String event,
String message,
Map<String, Object?> context,
)? logger;
final SpinifyLogger? logger;

@override
String toString() => 'SpinifyConfig{}';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/transport_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'reply.dart';

/// Create a Spinify transport
/// (e.g. WebSocket or gRPC with JSON or Protocol Buffers).
typedef CreateSpinifyTransport = Future<ISpinifyTransport> Function(
typedef SpinifyTransportBuilder = Future<ISpinifyTransport> Function(
/// URL for the connection
String url,

Expand Down
15 changes: 3 additions & 12 deletions lib/src/protobuf/protobuf_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
import '../model/channel_push.dart';
import '../model/client_info.dart';
import '../model/command.dart';
import '../model/config.dart';
import '../model/reply.dart';
import '../model/stream_position.dart';
import 'client.pb.dart' as pb;
Expand All @@ -30,12 +31,7 @@ final class ProtobufCommandEncoder
/// - [event] - the log event, unique type of log event
/// - [message] - the log message
/// - [context] - the log context data
final void Function(
int level,
String event,
String message,
Map<String, Object?> context,
)? logger;
final SpinifyLogger? logger;

@override
pb.Command convert(SpinifyCommand input) {
Expand Down Expand Up @@ -167,12 +163,7 @@ final class ProtobufReplyDecoder extends Converter<pb.Reply, SpinifyReply> {
/// - [event] - the log event, unique type of log event
/// - [message] - the log message
/// - [context] - the log context data
final void Function(
int level,
String event,
String message,
Map<String, Object?> context,
)? logger;
final SpinifyLogger? logger;

@override
SpinifyReply convert(pb.Reply input) {
Expand Down
44 changes: 22 additions & 22 deletions lib/src/spinify_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ abstract base class SpinifyBase implements ISpinify {
@nonVirtual
final SpinifyConfig config;

late final CreateSpinifyTransport _createTransport;
late final SpinifyTransportBuilder _createTransport;
ISpinifyTransport? _transport;

/// Client initialization (from constructor).
@mustCallSuper
void _init() {
_createTransport = $create$WS$PB$Transport;
config.logger?.call(
3,
const SpinifyLogLevel.info(),
'init',
'Spinify client initialized',
<String, Object?>{
Expand All @@ -66,7 +66,7 @@ abstract base class SpinifyBase implements ISpinify {
@mustCallSuper
Future<void> _onReply(SpinifyReply reply) async {
config.logger?.call(
0,
const SpinifyLogLevel.debug(),
'reply',
'Reply ${reply.type}{id: ${reply.id}} received',
<String, Object?>{
Expand All @@ -79,7 +79,7 @@ abstract base class SpinifyBase implements ISpinify {
@mustCallSuper
Future<void> _onDisconnected() async {
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'disconnected',
'Disconnected',
<String, Object?>{
Expand All @@ -91,7 +91,7 @@ abstract base class SpinifyBase implements ISpinify {
@override
Future<void> close() async {
config.logger?.call(
3,
const SpinifyLogLevel.info(),
'closed',
'Closed',
<String, Object?>{
Expand Down Expand Up @@ -120,7 +120,7 @@ base mixin SpinifyStateMixin on SpinifyBase {
final previous = _state;
_statesController.add(_state = state);
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'state_changed',
'State changed from $previous to $state',
<String, Object?>{
Expand Down Expand Up @@ -164,7 +164,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {

Future<T> _sendCommand<T extends SpinifyReply>(SpinifyCommand command) async {
config.logger?.call(
0,
const SpinifyLogLevel.debug(),
'send_command_begin',
'Command ${command.type}{id: ${command.id}} sent begin',
<String, Object?>{
Expand All @@ -181,7 +181,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
await _transport?.send(command); // await _sendCommandAsync(command);
final result = await completer.future.timeout(config.timeout);
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'send_command_success',
'Command ${command.type}{id: ${command.id}} sent successfully',
<String, Object?>{
Expand All @@ -195,7 +195,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
if (tuple != null && !tuple.completer.isCompleted) {
tuple.completer.completeError(error, stackTrace);
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'send_command_error',
'Error sending command ${command.type}{id: ${command.id}}',
<String, Object?>{
Expand All @@ -211,7 +211,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {

Future<void> _sendCommandAsync(SpinifyCommand command) async {
config.logger?.call(
0,
const SpinifyLogLevel.debug(),
'send_command_async_begin',
'Comand ${command.type}{id: ${command.id}} sent async begin',
<String, Object?>{
Expand All @@ -224,7 +224,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
assert(!state.isClosed, 'State is closed');
await _transport?.send(command);
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'send_command_async_success',
'Command sent ${command.type}{id: ${command.id}} async successfully',
<String, Object?>{
Expand All @@ -233,7 +233,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
);
} on Object catch (error, stackTrace) {
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'send_command_async_error',
'Error sending command ${command.type}{id: ${command.id}} async',
<String, Object?>{
Expand Down Expand Up @@ -267,7 +267,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
@override
Future<void> _onDisconnected() async {
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'disconnected',
'Disconnected from server',
<String, Object?>{},
Expand All @@ -278,7 +278,7 @@ base mixin SpinifyCommandMixin on SpinifyBase {
if (tuple.completer.isCompleted) continue;
tuple.completer.completeError(error);
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'disconnected_reply_error',
'Reply for command ${tuple.command.type}{id: ${tuple.command.id}} '
'error on disconnect',
Expand Down Expand Up @@ -362,7 +362,7 @@ base mixin SpinifyConnectionMixin
await _onConnected();

config.logger?.call(
2,
const SpinifyLogLevel.config(),
'connected',
'Connected to server with $url successfully',
<String, Object?>{
Expand All @@ -375,7 +375,7 @@ base mixin SpinifyConnectionMixin
if (!completer.isCompleted) completer.completeError(error, stackTrace);
_readyCompleter = null;
config.logger?.call(
5,
const SpinifyLogLevel.error(),
'connect_error',
'Error connecting to server $url',
<String, Object?>{
Expand Down Expand Up @@ -404,7 +404,7 @@ base mixin SpinifyConnectionMixin
final duration = ttl.difference(DateTime.now()) - config.timeout;
if (duration < Duration.zero) {
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'refresh_connection_cancelled',
'Spinify token TTL is too short for refresh connection',
<String, Object?>{
Expand All @@ -422,7 +422,7 @@ base mixin SpinifyConnectionMixin
if (token == null || token.isEmpty) {
assert(token == null || token.length > 5, 'Spinify JWT is too short');
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'refresh_connection_cancelled',
'Spinify JWT is empty or too short for refresh connection',
<String, Object?>{
Expand All @@ -442,7 +442,7 @@ base mixin SpinifyConnectionMixin
result = await _sendCommand<SpinifyRefreshResult>(request);
} on Object catch (error, stackTrace) {
config.logger?.call(
5,
const SpinifyLogLevel.error(),
'refresh_connection_error',
'Error refreshing connection',
<String, Object?>{
Expand All @@ -468,7 +468,7 @@ base mixin SpinifyConnectionMixin
));
_setUpRefreshConnection();
config.logger?.call(
2,
const SpinifyLogLevel.config(),
'refresh_connection_success',
'Successfully refreshed connection to $url',
<String, Object?>{
Expand Down Expand Up @@ -545,7 +545,7 @@ base mixin SpinifyPingPongMixin
// Reconnect if no pong received.
if (state case SpinifyState$Connected(:String url)) {
config.logger?.call(
4,
const SpinifyLogLevel.warning(),
'no_pong_reconnect',
'No pong from server - reconnecting',
<String, Object?>{
Expand Down Expand Up @@ -578,7 +578,7 @@ base mixin SpinifyPingPongMixin
final command = SpinifyPingRequest(timestamp: DateTime.now());
await _sendCommandAsync(command);
config.logger?.call(
0,
const SpinifyLogLevel.debug(),
'server_ping_received',
'Ping from server received, pong sent',
<String, Object?>{
Expand Down

0 comments on commit 2de2f82

Please sign in to comment.