<commit_before>//
// TorrentManager.cs
//
// Authors:
//   <NAME> <EMAIL>
//
// Copyright (C) 2006 <NAME>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//



using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using MonoTorrent.Client.PeerMessages;
using MonoTorrent.Common;
using System.Threading;
using System.IO;
using System.Xml.Serialization;
using System.Diagnostics;
using MonoTorrent.BEncoding;

namespace MonoTorrent.Client
{
    /// <summary>
    /// 
    /// </summary>
    public class TorrentManager : IDisposable, IEquatable<TorrentManager>
    {
        internal Queue<int> finishedPieces = new Queue<int>();
        #region Events
        /// <summary>
        /// Event that's fired every time new peers are added from a tracker update
        /// </summary>
        public event EventHandler<PeersAddedEventArgs> PeersFound;


        /// <summary>
        /// Event that's fired every time a piece is hashed
        /// </summary>
        public event EventHandler<PieceHashedEventArgs> PieceHashed;


        /// <summary>
        /// Event that's fired every time the TorrentManagers state changes
        /// </summary>
        public event EventHandler<TorrentStateChangedEventArgs> TorrentStateChanged;
        #endregion

        internal bool loadedFastResume;

        #region Member Variables
        public ConnectionMonitor Monitor
        {
            get { return this.monitor; }
        }
        private ConnectionMonitor monitor;

        internal BitField Bitfield
        {
            get { return this.bitfield; }
        }
        private BitField bitfield;


        /// <summary>
        /// The Torrent contained within this TorrentManager
        /// </summary>
        public Torrent Torrent
        {
            get { return this.torrent; }
        }
        private Torrent torrent;


        /// <summary>
        /// The settings for with this TorrentManager
        /// </summary>
        public TorrentSettings Settings
        {
            get { return this.settings; }
        }
        private TorrentSettings settings;


        /// <summary>
        /// The current state of the TorrentManager
        /// </summary>
        public TorrentState State
        {
            get { return this.state; }
        }
        private TorrentState state;


        /// <summary>
        /// The tracker connection associated with this TorrentManager
        /// </summary>
        public TrackerManager TrackerManager
        {
            get { return this.trackerManager; }
        }
        private TrackerManager trackerManager;


        /// <summary>
        /// The piecemanager for this TorrentManager
        /// </summary>
        public PieceManager PieceManager
        {
            get { return this.pieceManager; }
        }
        private PieceManager pieceManager;


        /// <summary>
        /// The DiskManager associated with this torrent
        /// </summary>
        internal FileManager FileManager
        {
            get { return this.fileManager; }
        }
        private FileManager fileManager;


        /// <summary>
        /// Contains the logic to decide how many chunks we can download
        /// </summary>
        private RateLimiter rateLimiter;


        /// <summary>
        /// The object we use to syncronize list access
        /// </summary>
        internal object listLock = new object();


        internal PeerList Peers
        {
            get { return this.peers; }
        }
        private PeerList peers;


        /// <summary>
        /// The number of peers that this torrent instance is connected to
        /// </summary>
        public int OpenConnections
        {
            get { return this.peers.ConnectedPeers.Count; }
        }


        /// <summary>
        /// True if this file has been hashchecked
        /// </summary>
        public bool HashChecked
        {
            get { return this.hashChecked; }
            internal set { this.hashChecked = value; }
        }
        private bool hashChecked;


        /// <summary>
        /// The number of times we recieved a piece that failed the hashcheck
        /// </summary>
        public int HashFails
        {
            get { return this.hashFails; }
        }
        private int hashFails;


        /// <summary>
        /// The directory to download the files to
        /// </summary>
        public string SavePath
        {
            get { return this.savePath; }
            set { this.savePath = value; }
        }
        private string savePath;


        /// <summary>
        /// The number of peers that we are currently uploading to
        /// </summary>
        public int UploadingTo
        {
            get { return this.uploadingTo; }
            internal set { this.uploadingTo = value; }
        }
        private int uploadingTo;
        #endregion


        #region Constructors
        /// <summary>
        /// Creates a new TorrentManager instance.
        /// </summary>
        /// <param name="torrent">The torrent to load in</param>
        /// <param name="savePath">The directory to save downloaded files to</param>
        /// <param name="settings">The settings to use for controlling connections</param>
        public TorrentManager(Torrent torrent, string savePath, TorrentSettings settings, EngineSettings engineSettings)
        {
            this.torrent = torrent;
            this.settings = settings;

            this.trackerManager = new TrackerManager(this, engineSettings);

            this.peers = new PeerList();

            this.savePath = savePath;

            if (string.IsNullOrEmpty(savePath))
                throw new TorrentException("Torrent savepath cannot be null");

            this.fileManager = new FileManager(this.torrent.Files, this.torrent.Name, this.savePath, this.torrent.PieceLength, System.IO.FileAccess.ReadWrite);

            this.bitfield = new BitField(this.torrent.Pieces.Count);
            this.pieceManager = new PieceManager(this.bitfield, (TorrentFile[])this.torrent.Files);
            this.monitor = new ConnectionMonitor();
        }
        #endregion


        #region Start/Stop/Pause
        /// <summary>
        /// Starts the TorrentManager
        /// </summary>
        internal void Start()
        {
            if (this.state == TorrentState.Paused)
            {
                UpdateState(TorrentState.Downloading);
                this.ResumePeers();
                return;
            }

            if(!this.fileManager.StreamsOpen)
                this.FileManager.OpenFileStreams(FileAccess.ReadWrite);

            if (this.fileManager.InitialHashRequired)
            {
                if (!this.hashChecked && !(this.state == TorrentState.Hashing))
                {
                    UpdateState(TorrentState.Hashing);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(HashCheck), this);
                    return;
                }

                else if (!this.hashChecked)
                {
                    return;
                }
            }

            this.fileManager.InitialHashRequired = false;
            if (this.state == TorrentState.Seeding || this.state == TorrentState.SuperSeeding || this.state == TorrentState.Downloading)
                throw new TorrentException("Torrent is already running");

            if (this.loadedFastResume)
            {
                for (int i = 0; i < this.bitfield.Length; i++)
                    if (PieceHashed != null)
                        PieceHashed(this, new PieceHashedEventArgs(i, this.bitfield[i]));

                this.loadedFastResume = false;
            }

            if (this.Progress == 100.0)
                UpdateState(TorrentState.Seeding);
            else
                UpdateState(TorrentState.Downloading);

            this.TrackerManager.Scrape();
            this.trackerManager.Announce(TorrentEvent.Started); // Tell server we're starting
            ClientEngine.ConnectionManager.RegisterManager(this);
        }


        /// <summary>
        /// Stops the TorrentManager
        /// </summary>
        internal WaitHandle Stop()
        {
            WaitHandle handle;

            UpdateState(TorrentState.Stopped);

            handle = this.trackerManager.Announce(TorrentEvent.Stopped);
            lock (this.listLock)
            {
                while (this.peers.ConnectingToPeers.Count > 0)
                    lock (this.peers.ConnectingToPeers[0])
                        ClientEngine.ConnectionManager.CleanupSocket(this.peers.ConnectingToPeers[0], true, "Called stop");

                while (this.peers.ConnectedPeers.Count > 0)
                    lock (this.peers.ConnectedPeers[0])
                        ClientEngine.ConnectionManager.CleanupSocket(this.peers.ConnectedPeers[0], true, "Called stop");
            }

            if(this.fileManager.StreamsOpen)
                this.FileManager.CloseFileStreams();<commit_msg>Updated the order in which things happen to remove a race condition where double announces could happen.<commit_after>//
// TorrentManager.cs
//
// Authors:
//   <NAME> <EMAIL>
//
// Copyright (C) 2006 <NAME>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//



using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using MonoTorrent.Client.PeerMessages;
using MonoTorrent.Common;
using System.Threading;
using System.IO;
using System.Xml.Serialization;
using System.Diagnostics;
using MonoTorrent.BEncoding;

namespace MonoTorrent.Client
{
    /// <summary>
    /// 
    /// </summary>
    public class TorrentManager : IDisposable, IEquatable<TorrentManager>
    {
        internal Queue<int> finishedPieces = new Queue<int>();
        #region Events
        /// <summary>
        /// Event that's fired every time new peers are added from a tracker update
        /// </summary>
        public event EventHandler<PeersAddedEventArgs> PeersFound;


        /// <summary>
        /// Event that's fired every time a piece is hashed
        /// </summary>
        public event EventHandler<PieceHashedEventArgs> PieceHashed;


        /// <summary>
        /// Event that's fired every time the TorrentManagers state changes
        /// </summary>
        public event EventHandler<TorrentStateChangedEventArgs> TorrentStateChanged;
        #endregion

        internal bool loadedFastResume;

        #region Member Variables
        public ConnectionMonitor Monitor
        {
            get { return this.monitor; }
        }
        private ConnectionMonitor monitor;

        internal BitField Bitfield
        {
            get { return this.bitfield; }
        }
        private BitField bitfield;


        /// <summary>
        /// The Torrent contained within this TorrentManager
        /// </summary>
        public Torrent Torrent
        {
            get { return this.torrent; }
        }
        private Torrent torrent;


        /// <summary>
        /// The settings for with this TorrentManager
        /// </summary>
        public TorrentSettings Settings
        {
            get { return this.settings; }
        }
        private TorrentSettings settings;


        /// <summary>
        /// The current state of the TorrentManager
        /// </summary>
        public TorrentState State
        {
            get { return this.state; }
        }
        private TorrentState state;


        /// <summary>
        /// The tracker connection associated with this TorrentManager
        /// </summary>
        public TrackerManager TrackerManager
        {
            get { return this.trackerManager; }
        }
        private TrackerManager trackerManager;


        /// <summary>
        /// The piecemanager for this TorrentManager
        /// </summary>
        public PieceManager PieceManager
        {
            get { return this.pieceManager; }
        }
        private PieceManager pieceManager;


        /// <summary>
        /// The DiskManager associated with this torrent
        /// </summary>
        internal FileManager FileManager
        {
            get { return this.fileManager; }
        }
        private FileManager fileManager;


        /// <summary>
        /// Contains the logic to decide how many chunks we can download
        /// </summary>
        private RateLimiter rateLimiter;


        /// <summary>
        /// The object we use to syncronize list access
        /// </summary>
        internal object listLock = new object();


        internal PeerList Peers
        {
            get { return this.peers; }
        }
        private PeerList peers;


        /// <summary>
        /// The number of peers that this torrent instance is connected to
        /// </summary>
        public int OpenConnections
        {
            get { return this.peers.ConnectedPeers.Count; }
        }


        /// <summary>
        /// True if this file has been hashchecked
        /// </summary>
        public bool HashChecked
        {
            get { return this.hashChecked; }
            internal set { this.hashChecked = value; }
        }
        private bool hashChecked;


        /// <summary>
        /// The number of times we recieved a piece that failed the hashcheck
        /// </summary>
        public int HashFails
        {
            get { return this.hashFails; }
        }
        private int hashFails;


        /// <summary>
        /// The directory to download the files to
        /// </summary>
        public string SavePath
        {
            get { return this.savePath; }
            set { this.savePath = value; }
        }
        private string savePath;


        /// <summary>
        /// The number of peers that we are currently uploading to
        /// </summary>
        public int UploadingTo
        {
            get { return this.uploadingTo; }
            internal set { this.uploadingTo = value; }
        }
        private int uploadingTo;
        #endregion


        #region Constructors
        /// <summary>
        /// Creates a new TorrentManager instance.
        /// </summary>
        /// <param name="torrent">The torrent to load in</param>
        /// <param name="savePath">The directory to save downloaded files to</param>
        /// <param name="settings">The settings to use for controlling connections</param>
        public TorrentManager(Torrent torrent, string savePath, TorrentSettings settings, EngineSettings engineSettings)
        {
            this.torrent = torrent;
            this.settings = settings;

            this.trackerManager = new TrackerManager(this, engineSettings);

            this.peers = new PeerList();

            this.savePath = savePath;

            if (string.IsNullOrEmpty(savePath))
                throw new TorrentException("Torrent savepath cannot be null");

            this.fileManager = new FileManager(this.torrent.Files, this.torrent.Name, this.savePath, this.torrent.PieceLength, System.IO.FileAccess.ReadWrite);

            this.bitfield = new BitField(this.torrent.Pieces.Count);
            this.pieceManager = new PieceManager(this.bitfield, (TorrentFile[])this.torrent.Files);
            this.monitor = new ConnectionMonitor();
        }
        #endregion


        #region Start/Stop/Pause
        /// <summary>
        /// Starts the TorrentManager
        /// </summary>
        internal void Start()
        {
            if (this.state == TorrentState.Paused)
            {
                UpdateState(TorrentState.Downloading);
                this.ResumePeers();
                return;
            }

            if(!this.fileManager.StreamsOpen)
                this.FileManager.OpenFileStreams(FileAccess.ReadWrite);

            if (this.fileManager.InitialHashRequired)
            {
                if (!this.hashChecked && !(this.state == TorrentState.Hashing))
                {
                    UpdateState(TorrentState.Hashing);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(HashCheck), this);
                    return;
                }

                else if (!this.hashChecked)
                {
                    return;
                }
            }

            this.fileManager.InitialHashRequired = false;
            if (this.state == TorrentState.Seeding || this.state == TorrentState.SuperSeeding || this.state == TorrentState.Downloading)
                throw new TorrentException("Torrent is already running");

            if (this.loadedFastResume)
            {
                for (int i = 0; i < this.bitfield.Length; i++)
                    if (PieceHashed != null)
                        PieceHashed(this, new PieceHashedEventArgs(i, this.bitfield[i]));

                this.loadedFastResume = false;
            }

			this.TrackerManager.Scrape();
			this.trackerManager.Announce(TorrentEvent.Started); // Tell server we're starting
			ClientEngine.ConnectionManager.RegisterManager(this);

            if (this.Progress == 100.0)
                UpdateState(TorrentState.Seeding);
            else
                UpdateState(TorrentState.Downloading);

        }


        /// <summary>
        /// Stops the TorrentManager
        /// </summary>
        internal WaitHandle Stop()
        {
            WaitHandle handle;

            UpdateState(TorrentState.Stopped);

            handle = this.trackerManager.Announce(TorrentEvent.Stopped);
            lock (this.listLock)
            {
                while (this.peers.ConnectingToPeers.Count > 0)
                    lock (this.peers.ConnectingToPeers[0])
                        ClientEngine.ConnectionManager.CleanupSocket(this.peers.ConnectingToPeers[0], true, "Called stop");

                while (this.peers.ConnectedPeers.Count > 0)
                    lock (this.peers.ConnectedPeers[0])
                        ClientEngine.ConnectionManager.CleanupSocket(this.peers.ConnectedPeers[0], true, "Called stop");
            }

            if(this.fileManager.StreamsOpen)
                this.FileManager.CloseFileStreams();
{'fraction_non_alphanumeric': 0.09112531589118478, 'fraction_numerical': 0.001486546751895347, 'mean_word_length': 1.769969805105682, 'pattern_counts': {'":': 0, '<': 115, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6211', 'n_tokens_mistral': 5165, 'n_tokens_neox': 4894, 'n_words': 1477}