| @@ -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> | |||