@@ -0,0 +1,15 @@ | |||
using System; | |||
namespace Discord.Commands | |||
{ | |||
public class CommandException : Exception | |||
{ | |||
public CommandInfo Command { get; } | |||
public ICommandContext Content { get; } | |||
public CommandException(CommandInfo command, ICommandContext context, Exception ex) | |||
: base($"Error occurred executing {command.GetLogText(context)}.", ex) | |||
{ | |||
} | |||
} | |||
} |
@@ -1,4 +1,6 @@ | |||
using System; | |||
using Discord.Commands.Builders; | |||
using Discord.Logging; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -7,12 +9,13 @@ using System.Reflection; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
namespace Discord.Commands | |||
{ | |||
public class CommandService | |||
{ | |||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | |||
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | |||
private readonly SemaphoreSlim _moduleLock; | |||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs; | |||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders; | |||
@@ -24,6 +27,8 @@ namespace Discord.Commands | |||
internal readonly bool _caseSensitive; | |||
internal readonly char _separatorChar; | |||
internal readonly RunMode _defaultRunMode; | |||
internal readonly Logger _cmdLogger; | |||
internal readonly LogManager _logManager; | |||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | |||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | |||
@@ -36,7 +41,11 @@ namespace Discord.Commands | |||
_separatorChar = config.SeparatorChar; | |||
_defaultRunMode = config.DefaultRunMode; | |||
if (_defaultRunMode == RunMode.Default) | |||
throw new InvalidOperationException("The default run mode cannot be set to Default, it must be one of Sync, Mixed, or Async"); | |||
throw new InvalidOperationException("The default run mode cannot be set to Default."); | |||
_logManager = new LogManager(config.LogLevel); | |||
_logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); | |||
_cmdLogger = _logManager.CreateLogger("Command"); | |||
_moduleLock = new SemaphoreSlim(1, 1); | |||
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||
@@ -8,5 +8,8 @@ | |||
public char SeparatorChar { get; set; } = ' '; | |||
/// <summary> Should commands be case-sensitive? </summary> | |||
public bool CaseSensitiveCommands { get; set; } = false; | |||
/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | |||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||
} | |||
} |
@@ -137,16 +137,48 @@ namespace Discord.Commands | |||
return ExecuteResult.FromError(result); | |||
} | |||
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | |||
switch (RunMode) | |||
{ | |||
case RunMode.Sync: //Always sync | |||
await _action(context, args, map).ConfigureAwait(false); | |||
try | |||
{ | |||
await _action(context, args, map).ConfigureAwait(false); | |||
} | |||
catch (Exception ex) | |||
{ | |||
ex = new CommandException(this, context, ex); | |||
await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||
throw; | |||
} | |||
await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||
break; | |||
case RunMode.Mixed: //Sync until first await statement | |||
var t1 = _action(context, args, map); | |||
var t1 = _action(context, args, map).ContinueWith(async t => | |||
{ | |||
if (t.IsFaulted) | |||
{ | |||
var ex = new CommandException(this, context, t.Exception); | |||
await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||
} | |||
else | |||
await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||
}); | |||
break; | |||
case RunMode.Async: //Always async | |||
var t2 = Task.Run(() => _action(context, args, map)); | |||
var t2 = Task.Run(() => | |||
{ | |||
var _ = _action(context, args, map).ContinueWith(async t => | |||
{ | |||
if (t.IsFaulted) | |||
{ | |||
var ex = new CommandException(this, context, t.Exception); | |||
await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false); | |||
} | |||
else | |||
await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false); | |||
}); | |||
}); | |||
break; | |||
} | |||
return ExecuteResult.FromSuccess(); | |||
@@ -189,5 +221,13 @@ namespace Discord.Commands | |||
private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList) | |||
=> paramsList.Cast<T>().ToArray(); | |||
internal string GetLogText(ICommandContext context) | |||
{ | |||
if (context.Guild != null) | |||
return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}"; | |||
else | |||
return $"\"{Name}\" for {context.User} in {context.Channel}"; | |||
} | |||
} | |||
} |
@@ -1,11 +1,9 @@ | |||
using Discord.Commands.Builders; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Threading.Tasks; | |||
using Discord.Commands.Builders; | |||
using System.Reflection; | |||
namespace Discord.Commands | |||
{ | |||
public class ParameterInfo | |||
@@ -22,7 +22,7 @@ namespace Discord | |||
/// <summary> Gets or sets how a request should act in the case of an error, by default. </summary> | |||
public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; | |||
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary> | |||
/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | |||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||
/// <summary> Gets or sets whether the initial log entry should be printed. </summary> | |||