/// <summary>
 /// Logs a message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 /// <remarks>
 /// It logs either a trace, debug, or warning message depending on the
 /// <see cref="FtpResponse.Code"/>.
 /// </remarks>
 public static void Log([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     if (response.Code >= 200 && response.Code < 300)
     {
         log.Trace(response);
     }
     else if (response.Code >= 300 && response.Code < 400)
     {
         log.Info(response);
     }
     else if (response.Code < 200)
     {
         log.Debug(response);
     }
     else
     {
         log.Warn(response);
     }
 }
        private void ProcessQueue(CancellationToken cancellationToken)
        {
            var handles = new[]
            {
                cancellationToken.WaitHandle, _event
            };

            _log?.Debug("Starting background transfer worker.");
            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    var handleIndex = WaitHandle.WaitAny(handles);
                    if (handleIndex == 0)
                    {
                        break;
                    }

                    HasData = true;

                    try
                    {
                        BackgroundTransferEntry backgroundTransferEntry;
                        while ((backgroundTransferEntry = GetNextEntry()) != null)
                        {
                            Debug.Assert(backgroundTransferEntry != null, "backgroundTransferEntry must not be null (internal error)");
                            var log = backgroundTransferEntry.Log;
                            var backgroundTransfer = backgroundTransferEntry.BackgroundTransfer;
                            try
                            {
                                var bt = backgroundTransfer;
                                log?.Info("Starting background transfer {0}", bt.TransferId);
                                backgroundTransferEntry.Status = BackgroundTransferStatus.Transferring;
                                var task          = bt.Start(cancellationToken);
                                var cancelledTask = task
                                                    .ContinueWith(
                                    t =>
                                {
                                    // Nothing to do
                                    log?.Warn("Background transfer {0} cancelled", bt.TransferId);
                                },
                                    TaskContinuationOptions.OnlyOnCanceled);
                                var faultedTask = task
                                                  .ContinueWith(
                                    t =>
                                {
                                    log?.Error(t.Exception, "Background transfer {0} faulted", bt.TransferId);
                                },
                                    TaskContinuationOptions.OnlyOnFaulted);
                                var completedTask = task
                                                    .ContinueWith(
                                    t =>
                                {
                                    // Nothing to do
                                    log?.Info("Completed background transfer {0}", bt.TransferId);
                                },
                                    TaskContinuationOptions.NotOnCanceled);

                                try
                                {
                                    Task.WaitAll(cancelledTask, faultedTask, completedTask);
                                }
                                catch (AggregateException ex) when(ex.InnerExceptions.All(x => x is TaskCanceledException))
                                {
                                    // Ignore AggregateException when it only contains TaskCancelledException
                                }

                                log?.Trace("Background transfer {0} finished", bt.TransferId);
                            }
                            catch (Exception ex)
                            {
                                log?.Error(ex, "Error during execution of background transfer {0}", backgroundTransfer.TransferId);
                            }
                            finally
                            {
                                backgroundTransfer.Dispose();
                            }

                            backgroundTransferEntry.Status = BackgroundTransferStatus.Finished;
                            CurrentEntry = null;
                        }
                    }
                    finally
                    {
                        HasData = false;
                    }
                }
                _log?.Info("Cancellation requested - stopping background transfer worker.");
            }
            finally
            {
                _log?.Debug("Background transfer worker stopped.");
                Queue.Dispose();
            }
        }