private void TraceExecution(ICommandInstance instance)
        {
            Trace.WriteLine($"Executing class command: {Name}");
            Trace.Indent();

            foreach (var parameter in Parameters)
            {
                Trace.WriteLine($"{parameter.Name}: {parameter.Property.GetValue(instance) ?? "<null>"}");
            }

            Trace.Unindent();
        }
        /// <inheritdoc/>
        /// <remarks>Once already started, calling this method again will cause commands to be re-loaded.</remarks>
        public async Task StartAsync(CancellationToken cancellationToken = default)
        {
            using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this._cts.Token))
            {
                await this._lock.WaitAsync(cts.Token).ConfigureAwait(false);

                try
                {
                    this._log?.LogDebug("Initializing commands");

                    // dispose commands since we're reloading them
                    this.DisposeCommands();

                    // ask loader to load from all specified assemblies and types
                    IEnumerable <ICommandInstanceDescriptor> descriptors = await _commandsLoader.LoadFromAssembliesAsync(_options.Assemblies ?? Enumerable.Empty <Assembly>(), cts.Token).ConfigureAwait(false);

                    descriptors = descriptors.Union(await _commandsLoader.LoadFromTypesAsync(_options.Classes.Select(t => t.GetTypeInfo()) ?? Enumerable.Empty <TypeInfo>(), cts.Token).ConfigureAwait(false));

                    // make sure there's no duplicates
                    descriptors = descriptors.Distinct();

                    // for each loaded command, handle pre-initialization and caching
                    foreach (ICommandInstanceDescriptor descriptor in descriptors)
                    {
                        CommandsHandlerAttribute handlerAttribute = descriptor.GetHandlerAttribute();

                        // check if handler is persistent. If so, request it from provider to pre-initialize
                        if (handlerAttribute?.IsPersistent == true)
                        {
                            this._log?.LogDebug("Pre-initializing command handler {Handler}", descriptor.GetHandlerType().Name);
                            this._handlerProvider.GetCommandHandler(descriptor, this._services);
                        }

                        // create all command instances
                        this._log?.LogDebug("Creating command instance {Name} from handler {Handler}", descriptor.Method.Name, descriptor.GetHandlerType().Name);
                        ICommandInitializer initializer = this._initializers.GetInitializer(descriptor.Attribute.GetType());
                        ICommandInstance    instance    = initializer.InitializeCommand(descriptor, _options);
                        this._commands.Add(descriptor, instance);
                    }

                    // mark as started
                    this._started = true;
                    this._log?.LogDebug("{Count} commands loaded", _commands.Count);
                }
                finally
                {
                    this._lock.Release();
                }
            }
        }
Пример #3
0
 public Task CmdAliasesAsync(CommandContext context, ICommandInstance instance)
 {
     if (instance is StandardCommandInstance standardInstance)
     {
         return(context.ReplyTextAsync($"Executed alias `{standardInstance.Text}`"));
     }
     else if (instance is RegexCommandInstance regexInstance)
     {
         return(context.ReplyTextAsync($"Executed alias `{regexInstance.Pattern}`"));
     }
     else
     {
         return(context.ReplyTextAsync($"Executed some alias ({instance.GetType().Name})"));
     }
 }
        private async Task <ICommandResult> ExecuteAsyncInternal(ICommandContext context, CancellationToken cancellationToken = default)
        {
            if (!this._started)
            {
                throw new InvalidOperationException($"This {this.GetType().Name} is not started yet");
            }
            using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this._cts.Token))
            {
                IEnumerable <KeyValuePair <ICommandInstanceDescriptor, ICommandInstance> > commandsCopy;
                // copying might not be the fastest thing to do, but it'll ensure that commands won't be changed out of the lock
                // locking only copying to prevent hangs if user is not careful with their commands, while still preventing race conditions with StartAsync
                await this._lock.WaitAsync(cts.Token).ConfigureAwait(false);

                try
                {
                    // order commands by priority
                    // try to get from concrete Descriptor if possible, as it should be precached and avoid additional reflection and thus faster
                    commandsCopy = this._commands.OrderByDescending(kvp => (kvp.Key is CommandInstanceDescriptor cid) ? cid.Priority : kvp.Key.GetPriority());
                }
                finally
                {
                    this._lock.Release();
                }

                using (IServiceScope serviceScope = this._services.CreateScope())
                {
                    // Because scope won't have fallback services, and commands might need them, combine scope with fallback services
                    // TODO: think of a nicer way to solve fallback services problem - I don't like it as it is now
                    IServiceProvider services = serviceScope.ServiceProvider;
                    if (!object.ReferenceEquals(services, this._fallbackServices))
                    {
                        services = CombinedServiceProvider.Combine(serviceScope.ServiceProvider, this._fallbackServices);
                    }

                    foreach (KeyValuePair <ICommandInstanceDescriptor, ICommandInstance> commandKvp in commandsCopy)
                    {
                        ICommandInstanceDescriptor command  = commandKvp.Key;
                        ICommandInstance           instance = commandKvp.Value;
                        using (this._log.BeginCommandScope(context, command.GetHandlerType().Name, command.Method.Name))
                        {
                            ICommandsHandlerProviderResult handlerResult = null;
                            try
                            {
                                cts.Token.ThrowIfCancellationRequested();

                                // check if the command should run at all - if not, skip
                                ICommandResult matchResult = await instance.CheckMatchAsync(context, services, cts.Token).ConfigureAwait(false);

                                if (!matchResult.IsSuccess)
                                {
                                    continue;
                                }

                                // initialize handler
                                this._log?.LogTrace("Initializing handler handler {Handler} for command {Name}", command.GetHandlerType().Name, command.Method.Name);
                                handlerResult = this._handlerProvider.GetCommandHandler(command, services);
                                if (handlerResult?.HandlerInstance == null)
                                {
                                    this._log?.LogError("Retrieving handler {Handler} for command {Name} has failed, command execution aborting", command.GetHandlerType().Name, command.Method.Name);
                                    return(CommandExecutionResult.FromException(new ArgumentNullException(nameof(ICommandsHandlerProviderResult.HandlerInstance),
                                                                                                          $"Retrieving handler {command.GetHandlerType().Name} for command {command.Method.Name} has failed, command execution aborting")));
                                }
                                this._log?.LogTrace("Executing command {Name} from handler {Handler}", command.Method.Name, command.GetHandlerType().Name);

                                // execute the command
                                ICommandResult executeResult = await instance.ExecuteAsync(context, services, matchResult, handlerResult.HandlerInstance, cts.Token).ConfigureAwait(false);

                                if (executeResult.Exception != null)
                                {
                                    this._log?.LogError(executeResult.Exception, "Exception when executing command {Name} from handler {Handler}", command.Method.Name, command.GetHandlerType().Name);
                                    return(executeResult);
                                }
                                if (executeResult is IMessagesCommandResult messagesResult && messagesResult.Messages?.Any() == true)
                                {
                                    this._log?.LogTrace("Sending command results messages as a command response");
                                    await context.ReplyTextAsync(string.Join("\n", messagesResult.Messages), cts.Token).ConfigureAwait(false);
                                }
                                return(executeResult);
                            }
                            // special error case: operation canceled
                            // operation canceled is normal, so it shouldn't be logged as error
                            catch (OperationCanceledException ex)
                            {
                                this._log?.LogWarning("Execution of command {Name} from handler {Handler} was cancelled", command.Method.Name, command.GetHandlerType().Name);
                                return(CommandExecutionResult.FromException(ex));
                            }
                            // special error case: responding when silenced
                            // bots almost always respond to a command - but if they're silenced, an exception will be thrown
                            // this is normal - so it shouldn't be logged as error. Warning max
                            catch (MessageSendingException ex) when
                                (this.LogSilencedException(ex, context, "Unhandled Exception when executing command {Name} from handler {Handler} - likely due to being silenced or spam filtered", command.Method.Name, command.GetHandlerType().Name))
                            {
                                return(CommandExecutionResult.FromException(ex));
                            }
                            // normal error case
                            catch (Exception ex) when(ex.LogAsError(_log, "Unhandled Exception when executing command {Name} from handler {Handler}", command.Method.Name, command.GetHandlerType().Name))
                            {
                                return(CommandExecutionResult.FromException(ex));
                            }
                            finally
                            {
                                // if handler is allocated, not persistent and disposable, let's dispose it
                                if (handlerResult?.Descriptor?.Attribute?.IsPersistent != true && handlerResult?.HandlerInstance is IDisposable disposableHandler)
                                {
                                    try { disposableHandler?.Dispose(); } catch { }
                                }
                            }
                        }
                    }
                }
                return(CommandExecutionResult.Failure);
            }
        }