public async Task AddPeers_PeerExchangeMessage_Private()
        {
            var peer = new byte[] { 192, 168, 0, 1, 100, 0 };
            var dotF = new byte[] { 1 << 0 | 1 << 2 }; // 0x1 means supports encryption, 0x2 means is a seeder
            var id   = PeerId.CreateNull(40);

            id.SupportsFastPeer   = true;
            id.SupportsLTMessages = true;

            var torrent = TestRig.CreatePrivate();

            using var engine = new ClientEngine(EngineSettingsBuilder.CreateForTests());
            var manager = await engine.AddAsync(torrent, "");

            manager.Mode = new DownloadMode(manager, DiskManager, ConnectionManager, Settings);
            var peersTask = new TaskCompletionSource <PeerExchangePeersAdded> ();

            manager.PeersFound += (o, e) => {
                if (e is PeerExchangePeersAdded args)
                {
                    peersTask.TrySetResult(args);
                }
            };

            var exchangeMessage = new PeerExchangeMessage(13, peer, dotF, null);

            manager.Mode.HandleMessage(id, exchangeMessage);

            var addedArgs = await peersTask.Task.WithTimeout();

            Assert.AreEqual(0, addedArgs.NewPeers, "#1");
        }
        public async Task AddPeers_Tracker_Private()
        {
            var torrent = TestRig.CreatePrivate();

            using var engine = new ClientEngine(EngineSettingsBuilder.CreateForTests());
            var manager = await engine.AddAsync(torrent, "");

            manager.SetTrackerManager(TrackerManager);

            var peersTask = new TaskCompletionSource <TrackerPeersAdded> ();

            manager.PeersFound += (o, e) => {
                if (e is TrackerPeersAdded args)
                {
                    peersTask.TrySetResult(args);
                }
            };

            await TrackerManager.AddTrackerAsync(new Uri ("http://test.tracker"));

            TrackerManager.RaiseAnnounceComplete(TrackerManager.Tiers.Single().ActiveTracker, true, new[] { new Peer("One", new Uri("ipv4://1.1.1.1:1111")), new Peer("Two", new Uri("ipv4://2.2.2.2:2222")) });

            var addedArgs = await peersTask.Task.WithTimeout();

            Assert.AreEqual(2, addedArgs.NewPeers, "#1");
            Assert.AreEqual(2, manager.Peers.AvailablePeers.Count, "#2");
        }
        public async Task MismatchedPeerId_PrivateTorrent()
        {
            var torrent = TestRig.CreatePrivate();

            using var engine = new ClientEngine(EngineSettingsBuilder.CreateForTests());
            var manager = await engine.AddAsync(torrent, "");

            manager.Mode = new DownloadMode(manager, DiskManager, ConnectionManager, Settings);
            var peer      = PeerId.CreateNull(manager.Bitfield.Length);
            var handshake = new HandshakeMessage(manager.InfoHash, new BEncodedString(Enumerable.Repeat('c', 20).ToArray()), VersionInfo.ProtocolStringV100, false);

            Assert.Throws <TorrentException> (() => manager.Mode.HandleMessage(peer, handshake));
        }
Esempio n. 4
0
        public async Task ScrapeWithTruncatedInfoHash()
        {
            var link = new MagnetLink(new InfoHashes(null, new InfoHash(new byte[32])));

            using var engine = new ClientEngine(EngineSettingsBuilder.CreateForTests());
            var manager = await engine.AddAsync(link, "");

            var args = new TrackerRequestFactory(manager).CreateScrape();

            Assert.AreEqual(20, args.InfoHash.Span.Length);
            Assert.IsTrue(manager.InfoHashes.Contains(args.InfoHash));
            Assert.IsNull(manager.InfoHashes.V1);
        }
Esempio n. 5
0
        public async Task EmptyPeerId_PrivateTorrent()
        {
            var torrent = TestRig.CreatePrivate();

            using var engine = new ClientEngine(EngineSettingsBuilder.CreateForTests());
            var manager = await engine.AddAsync(torrent, "");

            manager.Mode = new DownloadMode(manager, DiskManager, ConnectionManager, Settings);
            var peer = PeerId.CreateNull(manager.Bitfield.Length);

            peer.Peer.PeerId = null;
            var handshake = new HandshakeMessage(manager.InfoHashes.V1OrV2, new BEncodedString(Enumerable.Repeat('c', 20).ToArray()), Constants.ProtocolStringV100, false);

            manager.Mode.HandleMessage(peer, handshake, default);
            Assert.AreEqual(handshake.PeerId, peer.PeerID);
        }
Esempio n. 6
0
        private static async Task StartEngine()
        {
#if DEBUG
            Logger.Factory = (string className) => new TextLogger(Console.Out, className);
#endif
            Torrent torrent = null;

            // Create an instance of the engine.
            engine = new ClientEngine();

            // If the torrentsPath does not exist, we want to create it
            if (!Directory.Exists(torrentsPath))
            {
                Directory.CreateDirectory(torrentsPath);
            }

            // For each file in the torrents path that is a .torrent file, load it into the engine.
            foreach (string file in Directory.GetFiles(torrentsPath))
            {
                if (file.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase))
                {
                    try {
                        // Load the .torrent from the file into a Torrent instance
                        // You can use this to do preprocessing should you need to
                        torrent = await Torrent.LoadAsync(file);

                        Console.WriteLine(torrent.InfoHash.ToString());
                    } catch (Exception e) {
                        Console.Write("Couldn't decode {0}: ", file);
                        Console.WriteLine(e.Message);
                        continue;
                    }

                    // EngineSettings.AutoSaveLoadFastResume is enabled, so any cached fast resume
                    // data will be implicitly loaded. If fast resume data is found, the 'hash check'
                    // phase of starting a torrent can be skipped.
                    //
                    // TorrentSettingsBuilder can be used to modify the settings for this
                    // torrent.
                    var manager = await engine.AddAsync(torrent, downloadsPath);

                    // Store the torrent manager in our list so we can access it later
                    torrents.Add(manager);
                    manager.PeersFound += Manager_PeersFound;
                }
            }

            // If we loaded no torrents, just exist. The user can put files in the torrents directory and start
            // the client again
            if (torrents.Count == 0)
            {
                Console.WriteLine("No torrents found in the Torrents directory");
                Console.WriteLine("Exiting...");
                engine.Dispose();
                return;
            }

            // For each torrent manager we loaded and stored in our list, hook into the events
            // in the torrent manager and start the engine.
            foreach (TorrentManager manager in torrents)
            {
                manager.PeerConnected += (o, e) => {
                    lock (listener)
                        listener.WriteLine($"Connection succeeded: {e.Peer.Uri}");
                };
                manager.ConnectionAttemptFailed += (o, e) => {
                    lock (listener)
                        listener.WriteLine(
                            $"Connection failed: {e.Peer.ConnectionUri} - {e.Reason}");
                };
                // Every time a piece is hashed, this is fired.
                manager.PieceHashed += delegate(object o, PieceHashedEventArgs e) {
                    lock (listener)
                        listener.WriteLine($"Piece Hashed: {e.PieceIndex} - {(e.HashPassed ? "Pass" : "Fail")}");
                };

                // Every time the state changes (Stopped -> Seeding -> Downloading -> Hashing) this is fired
                manager.TorrentStateChanged += delegate(object o, TorrentStateChangedEventArgs e) {
                    lock (listener)
                        listener.WriteLine($"OldState: {e.OldState} NewState: {e.NewState}");
                };

                // Every time the tracker's state changes, this is fired
                manager.TrackerManager.AnnounceComplete += (sender, e) => {
                    listener.WriteLine($"{e.Successful}: {e.Tracker}");
                };

                // Start the torrentmanager. The file will then hash (if required) and begin downloading/seeding.
                // As EngineSettings.AutoSaveLoadDhtCache is enabled, any cached data will be loaded into the
                // Dht engine when the first torrent is started, enabling it to bootstrap more rapidly.
                await manager.StartAsync();
            }

            // While the torrents are still running, print out some stats to the screen.
            // Details for all the loaded torrent managers are shown.
            int           i       = 0;
            bool          running = true;
            StringBuilder sb      = new StringBuilder(1024);
            while (running)
            {
                if ((i++) % 10 == 0)
                {
                    sb.Remove(0, sb.Length);
                    running = torrents.Exists(m => m.State != TorrentState.Stopped);

                    AppendFormat(sb, $"Transfer Rate:      {engine.TotalDownloadSpeed / 1024.0:0.00}kB/sec down / {engine.TotalUploadSpeed / 1024.0:0.00}kB/sec up");
                    AppendFormat(sb, $"Memory Cache:       {engine.DiskManager.CacheBytesUsed / 1024.0:0.00}/{engine.Settings.DiskCacheBytes / 1024.0:0.00} kB");
                    AppendFormat(sb, $"Disk IO Rate:       {engine.DiskManager.ReadRate / 1024.0:0.00} kB/s read / {engine.DiskManager.WriteRate / 1024.0:0.00} kB/s write");
                    AppendFormat(sb, $"Disk IO Total:      {engine.DiskManager.TotalBytesRead / 1024.0:0.00} kB read / {engine.DiskManager.TotalBytesWritten / 1024.0:0.00} kB written");
                    AppendFormat(sb, $"Open Connections:   {engine.ConnectionManager.OpenConnections}");

                    // Print out the port mappings
                    foreach (var mapping in engine.PortMappings.Created)
                    {
                        AppendFormat(sb, $"Successful Mapping    {mapping.PublicPort}:{mapping.PrivatePort} ({mapping.Protocol})");
                    }
                    foreach (var mapping in engine.PortMappings.Failed)
                    {
                        AppendFormat(sb, $"Failed mapping:       {mapping.PublicPort}:{mapping.PrivatePort} ({mapping.Protocol})");
                    }
                    foreach (var mapping in engine.PortMappings.Pending)
                    {
                        AppendFormat(sb, $"Pending mapping:      {mapping.PublicPort}:{mapping.PrivatePort} ({mapping.Protocol})");
                    }

                    foreach (TorrentManager manager in torrents)
                    {
                        AppendSeparator(sb);
                        AppendFormat(sb, $"State:              {manager.State}");
                        AppendFormat(sb, $"Name:               {(manager.Torrent == null ? "MetaDataMode" : manager.Torrent.Name)}");
                        AppendFormat(sb, $"Progress:           {manager.Progress:0.00}");
                        AppendFormat(sb, $"Transfer Rate:      {manager.Monitor.DownloadSpeed / 1024.0:0.00}kB/s down / {manager.Monitor.UploadSpeed / 1024.0:0.00} kB/s up");
                        AppendFormat(sb, $"Total transferred:  {manager.Monitor.DataBytesDownloaded / (1024.0 * 1024.0):0.00} MB down / {manager.Monitor.DataBytesUploaded / (1024.0 * 1024.0):0.00} MB up");
                        AppendFormat(sb, $"Tracker Status");
                        foreach (var tier in manager.TrackerManager.Tiers)
                        {
                            AppendFormat(sb, $"\t{tier.ActiveTracker} : Announce Succeeded: {tier.LastAnnounceSucceeded}. Scrape Succeeded: {tier.LastScrapeSucceeded}.");
                        }

                        if (manager.PieceManager != null)
                        {
                            AppendFormat(sb, "Current Requests:   {0}", await manager.PieceManager.CurrentRequestCountAsync());
                        }

                        var peers = await manager.GetPeersAsync();

                        AppendFormat(sb, "Outgoing:");
                        foreach (PeerId p in peers.Where(t => t.ConnectionDirection == Direction.Outgoing))
                        {
                            AppendFormat(sb, "\t{2} - {1:0.00}/{3:0.00}kB/sec - {0} - {4} ({5})", p.Uri,
                                         p.Monitor.DownloadSpeed / 1024.0,
                                         p.AmRequestingPiecesCount,
                                         p.Monitor.UploadSpeed / 1024.0,
                                         p.EncryptionType,
                                         string.Join("|", p.SupportedEncryptionTypes.Select(t => t.ToString()).ToArray()));
                        }
                        AppendFormat(sb, "");
                        AppendFormat(sb, "Incoming:");
                        foreach (PeerId p in peers.Where(t => t.ConnectionDirection == Direction.Incoming))
                        {
                            AppendFormat(sb, "\t{2} - {1:0.00}/{3:0.00}kB/sec - {0} - {4} ({5})", p.Uri,
                                         p.Monitor.DownloadSpeed / 1024.0,
                                         p.AmRequestingPiecesCount,
                                         p.Monitor.UploadSpeed / 1024.0,
                                         p.EncryptionType,
                                         string.Join("|", p.SupportedEncryptionTypes.Select(t => t.ToString()).ToArray()));
                        }

                        AppendFormat(sb, "", null);
                        if (manager.Torrent != null)
                        {
                            foreach (var file in manager.Files)
                            {
                                AppendFormat(sb, "{1:0.00}% - {0}", file.Path, file.BitField.PercentComplete);
                            }
                        }
                    }
                    Console.Clear();
                    Console.WriteLine(sb.ToString());
                    listener.ExportTo(Console.Out);
                }

                Thread.Sleep(500);
            }
        }
Esempio n. 7
0
        public async Task RunAsync()
        {
            //LoggerFactory.Creator = className => new TextLogger (Console.Out, className);

            int port   = 37827;
            var seeder = new ClientEngine(
                new EngineSettingsBuilder {
                AllowedEncryption = new[] { EncryptionType.PlainText },
                DiskCacheBytes    = DataSize,
                ListenEndPoint    = new IPEndPoint(IPAddress.Any, port++)
            }.ToSettings(),
                Factories.Default.WithPieceWriterCreator(maxOpenFiles => new NullWriter())
                );

            var downloaders = Enumerable.Range(port, 16).Select(p => {
                return(new ClientEngine(
                           new EngineSettingsBuilder {
                    AllowedEncryption = new[] { EncryptionType.PlainText },
                    DiskCacheBytes = DataSize,
                    ListenEndPoint = new IPEndPoint(IPAddress.Any, p),
                }.ToSettings(),
                           Factories.Default.WithPieceWriterCreator(maxOpenFiles => new NullWriter())
                           ));
            }).ToArray();

            Directory.CreateDirectory(DataDir);
            // Generate some fake data on-disk
            var buffer = Enumerable.Range(0, 16 * 1024).Select(s => (byte)s).ToArray();

            using (var fileStream = File.OpenWrite(Path.Combine(DataDir, "file.data"))) {
                for (int i = 0; i < DataSize / buffer.Length; i++)
                {
                    fileStream.Write(buffer, 0, buffer.Length);
                }
                fileStream.SetLength(DataSize);
            }

            var trackerListener = TrackerListenerFactory.CreateHttp(IPAddress.Parse("127.0.0.1"), 25611);
            var tracker         = new TrackerServer {
                AllowUnregisteredTorrents = true
            };

            tracker.RegisterListener(trackerListener);
            trackerListener.Start();

            // Create the torrent file for the fake data
            var creator = new TorrentCreator();

            creator.Announces.Add(new List <string> ());
            creator.Announces[0].Add("http://127.0.0.1:25611/announce");

            var metadata = await creator.CreateAsync(new TorrentFileSource (DataDir));

            // Set up the seeder
            await seeder.AddAsync(Torrent.Load(metadata), DataDir, new TorrentSettingsBuilder { UploadSlots = 20 }.ToSettings());

            using (var fileStream = File.OpenRead(Path.Combine(DataDir, "file.data"))) {
                while (fileStream.Position < fileStream.Length)
                {
                    var dataRead = new byte[16 * 1024];
                    int offset   = (int)fileStream.Position;
                    int read     = fileStream.Read(dataRead, 0, dataRead.Length);
                    // FIXME: Implement a custom IPieceWriter to handle this.
                    // The internal MemoryWriter is limited and isn't a general purpose read/write API
                    // await seederWriter.WriteAsync (seeder.Torrents[0].Files[0], offset, dataRead, 0, read, false);
                }
            }

            await seeder.StartAllAsync();

            List <Task> tasks = new List <Task> ();

            for (int i = 0; i < downloaders.Length; i++)
            {
                await downloaders[i].AddAsync(
                    Torrent.Load(metadata),
                    Path.Combine(DataDir, "Downloader" + i)
                    );

                tasks.Add(RepeatDownload(downloaders[i]));
            }

            while (true)
            {
                long downTotal        = seeder.TotalDownloadRate;
                long upTotal          = seeder.TotalUploadRate;
                long totalConnections = 0;
                long dataDown         = seeder.Torrents[0].Monitor.DataBytesReceived + seeder.Torrents[0].Monitor.ProtocolBytesReceived;
                long dataUp           = seeder.Torrents[0].Monitor.DataBytesSent + seeder.Torrents[0].Monitor.ProtocolBytesSent;
                foreach (var engine in downloaders)
                {
                    downTotal += engine.TotalDownloadRate;
                    upTotal   += engine.TotalUploadRate;

                    dataDown         += engine.Torrents[0].Monitor.DataBytesReceived + engine.Torrents[0].Monitor.ProtocolBytesReceived;
                    dataUp           += engine.Torrents[0].Monitor.DataBytesSent + engine.Torrents[0].Monitor.ProtocolBytesSent;
                    totalConnections += engine.ConnectionManager.OpenConnections;
                }
                Console.Clear();
                Console.WriteLine($"Speed Down:        {downTotal / 1024 / 1024}MB.");
                Console.WriteLine($"Speed Up:          {upTotal / 1024 / 1024}MB.");
                Console.WriteLine($"Data Down:          {dataDown / 1024 / 1024}MB.");
                Console.WriteLine($"Data Up:            {dataUp / 1024 / 1024}MB.");

                Console.WriteLine($"Total Connections: {totalConnections}");
                await Task.Delay(3000);
            }
        }
Esempio n. 8
0
        private static async Task StartEngine()
        {
#if DEBUG
            Logger.Factory = (string className) => new TextLogger(Console.Out, className);
#endif
            int     port;
            Torrent torrent = null;
            // Ask the user what port they want to use for incoming connections
            Console.Write($"{Environment.NewLine}Choose a listen port: ");
            while (!Int32.TryParse(Console.ReadLine(), out port))
            {
            }

            // Create the settings which the engine will use
            // downloadsPath - this is the path where we will save all the files to
            // port - this is the port we listen for connections on
            EngineSettings engineSettings = new EngineSettingsBuilder {
                CacheDirectory = "cache",
                ListenPort     = port,
                DhtPort        = port,
                DiskCacheBytes = 5 * 1024 * 1024,
            }.ToSettings();

            //engineSettings.GlobalMaxUploadSpeed = 30 * 1024;
            //engineSettings.GlobalMaxDownloadSpeed = 100 * 1024;
            //engineSettings.MaxReadRate = 1 * 1024 * 1024;

            // Create the default settings which a torrent will have.
            TorrentSettings torrentDefaults = new TorrentSettings();

            // Create an instance of the engine.
            engine = new ClientEngine(engineSettings);

            byte[] nodes = Array.Empty <byte> ();
            try {
                if (File.Exists(dhtNodeFile))
                {
                    nodes = File.ReadAllBytes(dhtNodeFile);
                }
            } catch {
                Console.WriteLine("No existing dht nodes could be loaded");
            }


            // This starts the Dht engine but does not wait for the full initialization to
            // complete. This is because it can take up to 2 minutes to bootstrap, depending
            // on how many nodes time out when they are contacted.
            await engine.DhtEngine.StartAsync(nodes);

            // If the torrentsPath does not exist, we want to create it
            if (!Directory.Exists(torrentsPath))
            {
                Directory.CreateDirectory(torrentsPath);
            }

            BEncodedDictionary fastResume = new BEncodedDictionary();
            try {
                if (File.Exists(fastResumeFile))
                {
                    fastResume = BEncodedValue.Decode <BEncodedDictionary> (File.ReadAllBytes(fastResumeFile));
                }
            } catch {
            }

            // For each file in the torrents path that is a .torrent file, load it into the engine.
            foreach (string file in Directory.GetFiles(torrentsPath))
            {
                if (file.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase))
                {
                    try {
                        // Load the .torrent from the file into a Torrent instance
                        // You can use this to do preprocessing should you need to
                        torrent = await Torrent.LoadAsync(file);

                        Console.WriteLine(torrent.InfoHash.ToString());
                    } catch (Exception e) {
                        Console.Write("Couldn't decode {0}: ", file);
                        Console.WriteLine(e.Message);
                        continue;
                    }

                    var manager = await engine.AddAsync(torrent, downloadsPath, torrentDefaults);

                    // When any preprocessing has been completed, you create a TorrentManager
                    // which you then register with the engine.
                    if (fastResume.ContainsKey(torrent.InfoHash.ToHex()))
                    {
                        manager.LoadFastResume(new FastResume((BEncodedDictionary)fastResume[torrent.InfoHash.ToHex()]));
                    }

                    // Store the torrent manager in our list so we can access it later
                    torrents.Add(manager);
                    manager.PeersFound += Manager_PeersFound;
                }
            }

            // If we loaded no torrents, just exist. The user can put files in the torrents directory and start
            // the client again
            if (torrents.Count == 0)
            {
                Console.WriteLine("No torrents found in the Torrents directory");
                Console.WriteLine("Exiting...");
                engine.Dispose();
                return;
            }

            // For each torrent manager we loaded and stored in our list, hook into the events
            // in the torrent manager and start the engine.
            foreach (TorrentManager manager in torrents)
            {
                manager.PeerConnected += (o, e) => {
                    lock (listener)
                        listener.WriteLine($"Connection succeeded: {e.Peer.Uri}");
                };
                manager.ConnectionAttemptFailed += (o, e) => {
                    lock (listener)
                        listener.WriteLine(
                            $"Connection failed: {e.Peer.ConnectionUri} - {e.Reason}");
                };
                // Every time a piece is hashed, this is fired.
                manager.PieceHashed += delegate(object o, PieceHashedEventArgs e) {
                    lock (listener)
                        listener.WriteLine($"Piece Hashed: {e.PieceIndex} - {(e.HashPassed ? "Pass" : "Fail")}");
                };

                // Every time the state changes (Stopped -> Seeding -> Downloading -> Hashing) this is fired
                manager.TorrentStateChanged += delegate(object o, TorrentStateChangedEventArgs e) {
                    lock (listener)
                        listener.WriteLine($"OldState: {e.OldState} NewState: {e.NewState}");
                };

                // Every time the tracker's state changes, this is fired
                manager.TrackerManager.AnnounceComplete += (sender, e) => {
                    listener.WriteLine($"{e.Successful}: {e.Tracker}");
                };

                // Start the torrentmanager. The file will then hash (if required) and begin downloading/seeding
                await manager.StartAsync();
            }

            // This is how to access the list of port mappings, and to see if they were
            // successful, pending or failed. If they failed it could be because the public port
            // is already in use by another computer on your network.
            foreach (var successfulMapping in engine.PortMappings.Created)
            {
            }
            foreach (var failedMapping in engine.PortMappings.Failed)
            {
            }
            foreach (var failedMapping in engine.PortMappings.Pending)
            {
            }

            // While the torrents are still running, print out some stats to the screen.
            // Details for all the loaded torrent managers are shown.
            int           i       = 0;
            bool          running = true;
            StringBuilder sb      = new StringBuilder(1024);
            while (running)
            {
                if ((i++) % 10 == 0)
                {
                    sb.Remove(0, sb.Length);
                    running = torrents.Exists(m => m.State != TorrentState.Stopped);

                    AppendFormat(sb, "Total Download Rate: {0:0.00}kB/sec", engine.TotalDownloadSpeed / 1024.0);
                    AppendFormat(sb, "Total Upload Rate:   {0:0.00}kB/sec", engine.TotalUploadSpeed / 1024.0);
                    AppendFormat(sb, "Disk Read Rate:      {0:0.00} kB/s", engine.DiskManager.ReadRate / 1024.0);
                    AppendFormat(sb, "Disk Write Rate:     {0:0.00} kB/s", engine.DiskManager.WriteRate / 1024.0);
                    AppendFormat(sb, "Cache Used:         {0:0.00} kB", engine.DiskManager.CacheBytesUsed / 1024.0);
                    AppendFormat(sb, "Cache Read:         {0:0.00} kB", engine.DiskManager.TotalCacheBytesRead / 1024.0);
                    AppendFormat(sb, "Total Read:         {0:0.00} kB", engine.DiskManager.TotalBytesRead / 1024.0);
                    AppendFormat(sb, "Total Written:      {0:0.00} kB", engine.DiskManager.TotalBytesWritten / 1024.0);
                    AppendFormat(sb, "Open Connections:    {0}", engine.ConnectionManager.OpenConnections);

                    foreach (TorrentManager manager in torrents)
                    {
                        AppendSeparator(sb);
                        AppendFormat(sb, "State:           {0}", manager.State);
                        AppendFormat(sb, "Name:            {0}", manager.Torrent == null ? "MetaDataMode" : manager.Torrent.Name);
                        AppendFormat(sb, "Progress:           {0:0.00}", manager.Progress);
                        AppendFormat(sb, "Download Speed:     {0:0.00} kB/s", manager.Monitor.DownloadSpeed / 1024.0);
                        AppendFormat(sb, "Upload Speed:       {0:0.00} kB/s", manager.Monitor.UploadSpeed / 1024.0);
                        AppendFormat(sb, "Total Downloaded:   {0:0.00} MB", manager.Monitor.DataBytesDownloaded / (1024.0 * 1024.0));
                        AppendFormat(sb, "Total Uploaded:     {0:0.00} MB", manager.Monitor.DataBytesUploaded / (1024.0 * 1024.0));
                        AppendFormat(sb, "Tracker Status");
                        foreach (var tier in manager.TrackerManager.Tiers)
                        {
                            AppendFormat(sb, $"\t{tier.ActiveTracker} : Announce Succeeded: {tier.LastAnnounceSucceeded}. Scrape Succeeded: {tier.LastScrapeSucceeded}.");
                        }
                        if (manager.PieceManager != null)
                        {
                            AppendFormat(sb, "Current Requests:   {0}", await manager.PieceManager.CurrentRequestCountAsync());
                        }

                        var peers = await manager.GetPeersAsync();

                        AppendFormat(sb, "Outgoing:");
                        foreach (PeerId p in peers.Where(t => t.ConnectionDirection == Direction.Outgoing))
                        {
                            AppendFormat(sb, "\t{2} - {1:0.00}/{3:0.00}kB/sec - {0} - {4} ({5})", p.Uri,
                                         p.Monitor.DownloadSpeed / 1024.0,
                                         p.AmRequestingPiecesCount,
                                         p.Monitor.UploadSpeed / 1024.0,
                                         p.EncryptionType,
                                         string.Join("|", p.SupportedEncryptionTypes.Select(t => t.ToString()).ToArray()));
                        }
                        AppendFormat(sb, "");
                        AppendFormat(sb, "Incoming:");
                        foreach (PeerId p in peers.Where(t => t.ConnectionDirection == Direction.Incoming))
                        {
                            AppendFormat(sb, "\t{2} - {1:0.00}/{3:0.00}kB/sec - {0} - {4} ({5})", p.Uri,
                                         p.Monitor.DownloadSpeed / 1024.0,
                                         p.AmRequestingPiecesCount,
                                         p.Monitor.UploadSpeed / 1024.0,
                                         p.EncryptionType,
                                         string.Join("|", p.SupportedEncryptionTypes.Select(t => t.ToString()).ToArray()));
                        }

                        AppendFormat(sb, "", null);
                        if (manager.Torrent != null)
                        {
                            foreach (var file in manager.Files)
                            {
                                AppendFormat(sb, "{1:0.00}% - {0}", file.Path, file.BitField.PercentComplete);
                            }
                        }
                    }
                    Console.Clear();
                    Console.WriteLine(sb.ToString());
                    listener.ExportTo(Console.Out);
                }

                Thread.Sleep(500);
            }
        }