<commit_before>//
// TorrentCreator.cs
//
// Authors:
//   <NAME> <EMAIL>
//   <NAME> <EMAIL>
//
// Copyright (C) 2006-2007 <NAME> and <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.IO;
using System.Text;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using MonoTorrent.BEncoding;
using System.Threading;


namespace MonoTorrent.Common
{
    public class TorrentCreatorAsyncResult : IAsyncResult
    {
        #region Member Variables

        private bool aborted;
        private object asyncState;
        private AsyncCallback callback;
        private bool completedSynchronously;
        private bool isCompleted;
        private Exception savedException;
        private ManualResetEvent waitHandle;

        #endregion Member Variables


        #region Properties

        public object AsyncState
        {
            get { return asyncState; }
        }

        public bool Aborted
        {
            get { return this.aborted; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return waitHandle; }
        }

        internal AsyncCallback Callback
        {
            get { return callback; }
        }

        public bool CompletedSynchronously
        {
            get { return completedSynchronously; }
            internal set { completedSynchronously = value; }
        }

        public bool IsCompleted
        {
            get { return isCompleted; }
            internal set { isCompleted = value; }
        }

        internal Exception SavedException
        {
            get { return this.savedException; }
            set { this.savedException = value; }
        }

        #endregion Properties


        #region Constructors

        public TorrentCreatorAsyncResult(object asyncState, AsyncCallback callback)
        {
            this.asyncState = asyncState;
            this.callback = callback;
            this.waitHandle = new ManualResetEvent(false);
        }

        #endregion Constructors


        #region Methods

        public void Abort()
        {
            this.aborted = true;
        }

        #endregion Methods
    }


    ///<summary>
    ///this class is used to create on disk torrent files.
    ///</summary>
    public class TorrentCreator
    {
        #region Events

        public event EventHandler<TorrentCreatorEventArgs> Hashed;

        #endregion


        #region Private Fields

        private List<MonoTorrentCollection<string>> announces;      // The list of announce urls
        private bool ignoreHiddenFiles;                             // True if you want to ignore hidden files when making the torrent
        private string path;                                        // The path from which the torrent will be created (can be file or directory)
        private TorrentCreatorAsyncResult result;                   // The IAsyncResult generated by creating the torrent
        private bool storeMd5;                                      // True if an MD5 hash of each file should be included
        private BEncodedDictionary torrent;                         // The BencodedDictionary which contains the data to be written to the .torrent file

        #endregion Private Fields


        #region Properties

        /// <summary>
        /// The list of announce urls for this 
        /// </summary>
        public List<MonoTorrentCollection<string>> Announces
        {
            get { return this.announces; }
        }


        ///<summary>
        ///this property sets the comment entry in the torrent file. default is string.Empty to conserve bandwidth.
        ///</summary>
        public string Comment
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("comment"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "comment", new BEncodedString(value)); }
        }


        ///<summary>this property sets the created by entrie in the torrent file
        ///</summary>
        public string CreatedBy
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("created by"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "created by", new BEncodedString(value)); }
        }


        /// <summary>
        ///  The encoding used to create the torrent
        /// </summary>
        public string Encoding
        {
            get { return Get(this.torrent, new BEncodedString("encoding")).ToString(); }
        }


        /// <summary>
        /// The path from which you would like to make a torrent of.
        ///Path can be a file and a directory. 
        /// </summary>
        public string Path
        {
            get { return path; }
            set { path = value; }

        }


        ///<summary>
        ///this property sets the length of the pieces. default is 512 kb.
        ///</summary>
        public long PieceLength
        {
            get
            {
                BEncodedValue val = Get((BEncodedDictionary)this.torrent["info"], new BEncodedString("piece length"));
                return val == null ? -1 : ((BEncodedNumber)val).Number;
            }
            set { Set((BEncodedDictionary)this.torrent["info"], "piece length", new BEncodedNumber(value)); }
        }


        ///<summary>
        ///This property sets whether this is a private torrent or not
        ///</summary>
        public bool Private
        {
            get
            {
                BEncodedValue val = Get((BEncodedDictionary)this.torrent["info"], new BEncodedString("private"));
                return val == null ? false : ((BEncodedNumber)val).Number == 1;
            }
            set { Set((BEncodedDictionary)this.torrent["info"], "private", new BEncodedNumber(value ? 1 : 0)); }
        }


        public string Publisher
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("publisher"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "publisher", new BEncodedString(value)); }
        }


        ///<summary>this property sets the Publisher Url entry
        ///</summary>
        public string PublisherUrl
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("publisher-url"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "publisher-url", new BEncodedString(value)); }
        }


        ///<summary>this property sets wheather the optional field md5sum should be included. 
        ///default is false to conserve bandwidth
        ///</summary>
        public bool StoreMD5
        {
            get { return storeMd5; }
            set { storeMd5 = value; }
        }


        public bool IgnoreHiddenFiles
        {
            get { return ignoreHiddenFiles; }
            set { ignoreHiddenFiles = value; }
        }

        #endregion Properties


        #region Constructors

        public TorrentCreator()
        {
            BEncodedDictionary info = new BEncodedDictionary();
            this.announces = new List<MonoTorrentCollection<string>>();
            this.ignoreHiddenFiles = true;
            this.torrent = new BEncodedDictionary();
            this.torrent.Add("info", info);

            // Add in initial values for some of the torrent attributes
            PieceLength =  256 * 1024;   // 256kB default piece size
            torrent.Add("encoding", (BEncodedString)"UTF-8");
        }

        #endregion Constructors


        #region Methods

        ///<summary>
        ///this method runs recursively through all subdirs under dir and ads information
        ///from each file to the filesList. 
        ///<summary>
        ///<param name="dir">the top directory to start from</param>
        ///<param name="filesList">the list to store the file information</param>
        ///<param name="paths">a list of all files found. used for piece hashing later</param>
        private void AddAllFileInfoDicts(string dir, BEncodedList filesList, MonoTorrentCollection<string> paths)
        {
            GetAllFilePaths(dir, paths);

            for(int i=0; i < paths.Count; i++)
                filesList.Add(GetFileInfoDict(paths[i], dir));
        }


        private void GetAllFilePaths(string directory, MonoTorrentCollection<string> paths)
        {
            string[] subs = Directory.GetFileSystemEntries(directory);
            foreach (string path in subs)
            {
                if (Directory.Exists(path))
                {
                    if (ignoreHiddenFiles)
                    {
                        DirectoryInfo info = new DirectoryInfo(path);
                        if ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                            continue;
                    }

                    GetAllFilePaths(path, paths);
                }
                else
                {
                    if (ignoreHiddenFiles)
                    {
                        FileInfo info = new FileInfo(path);
                        if ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                            continue;
                    }

                    paths.Add(path);
                }
            }
        }


        ///<summary>
        ///this adds stuff common to single and multi file torrents
        ///</summary>
        private void AddCommonStuff(BEncodedDictionary torrent)
        {
            Debug.WriteLine(announces[0][0]);
            torrent.Add("announce", new BEncodedString(announces[0][0]));

            // If there is more than one tier or the first tier has more than 1 tracker
            if (announces.Count > 1 || announces[0].Count > 0)
            {
                BEncodedList announceList = new BEncodedList();
                for (int i = 0; i < this.announces.Count; i++)
                {
                    BEncodedList tier = new BEncodedList();
                    for (int j = 0; j < this.announces[i].Count; j++)
                        tier.Add(new BEncodedString(this.announces[i][j]));

                    announceList.Add(tier);
                }

                torrent.Add("announce-list", announceList);
            }


            DateTime epocheStart = new DateTime(1970, 1, 1);
            TimeSpan span = DateTime.Now - epocheStart;
            Debug.WriteLine("creation date: " + DateTime.Now.ToString() + " - " + epocheStart.ToString() + " = " + span.ToString() + " : " + (long)span.TotalSeconds);
            torrent.Add("creation date", new BEncodedNumber((long)span.TotalSeconds));
        }


        /// <summary>
        /// This can be used to add custom values to the main bencoded dictionary
        /// </summary>        
        public void AddCustom(BEncodedString key, BEncodedValue value)
        {
            this.torrent.Add(key, value);
        }


        /// <summary>
        /// This can be used to add custom values to the main bencoded dictionary
        /// </summary>        
        public void AddCustom(KeyValuePair<BEncodedString, BEncodedValue> customInfo)
        {
            this.torrent.Add(customInfo);
        }


        ///<summary>calculate md5sum of a file</summary>
        ///<param name="fileName">the file to sum with md5</param>
        private void AddMD5(BEncodedDictionary dict, string fileName)
        {
            MD5 hasher = MD5.Create();
            StringBuilder sb = new StringBuilder();

            using (FileStream stream = new FileStream(fileName, FileMode.Open))
            {
                byte[] hash = hasher.ComputeHash(stream);

                foreach (byte b in hash)
                {
                    string hex = b.ToString("X");
                    hex = hex.Length > 1 ? hex : "0" + hex;
                    sb.Append(hex);
                }
                Console.WriteLine("sum for file " + fileName + " = " + sb.ToString());
            }
            dict.Add("md5sum", new BEncodedString(sb.ToString()));
        }


        public TorrentCreatorAsyncResult BeginCreate(object asyncState, AsyncCallback callback)
        {
            if (result != null)
                throw new TorrentException("You must call EndCreate before calling BeginCreate again");

            result = new TorrentCreatorAsyncResult(asyncState, callback);

            // Start the processing in a seperate thread, a user thread.
            new Thread(new ThreadStart(AsyncCreate)).Start();
            return result;
        }

        public BEncodedDictionary EndCreate(IAsyncResult result)
        {

            if (result != this.result)
                throw new ArgumentException("The supplied async result does not correspond to currently active async result");
            try
            {
                if (this.result.SavedException != null)
                    throw this.result.SavedException;

                return this.torrent;
            }
            finally
            {
                this.result = null;
            }
        }

        private void AsyncCreate()
        {
            try
            {
                Create();
            }
            catch (Exception ex)
            {
                result.SavedException = ex;
            }

            result.IsCompleted = true;
            result.CompletedSynchronously = false;
            ((ManualResetEvent)result.AsyncWaitHandle).Set();

            if (result.Callback != null)
                result.Callback(this.result);
        }

        ///<summary>
        ///calculates all hashes over the files which should be included in the torrent
        ///</summmary>
        private byte[] CalcPiecesHash(MonoTorrentCollection<string> fullPaths)
        {
            SHA1 hasher = SHA1.Create();
            byte[] piece = new byte[PieceLength];//holds one piece for sha1 calcing
            int len = 0;        //holds the bytes read by the stream.Read() method
            byte[] piecesBuffer = new byte[GetPieceCount(fullPaths) * 20]; //holds all the pieces hashes
            int piecesBufferOffset = 0;<commit_msg>It'll never be synchronous<commit_after>//
// TorrentCreator.cs
//
// Authors:
//   <NAME> <EMAIL>
//   <NAME> <EMAIL>
//
// Copyright (C) 2006-2007 <NAME> and <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.IO;
using System.Text;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using MonoTorrent.BEncoding;
using System.Threading;


namespace MonoTorrent.Common
{
    public class TorrentCreatorAsyncResult : IAsyncResult
    {
        #region Member Variables

        private bool aborted;
        private object asyncState;
        private AsyncCallback callback;
        private bool isCompleted;
        private Exception savedException;
        private ManualResetEvent waitHandle;

        #endregion Member Variables


        #region Properties

        public object AsyncState
        {
            get { return asyncState; }
        }

        public bool Aborted
        {
            get { return this.aborted; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return waitHandle; }
        }

        internal AsyncCallback Callback
        {
            get { return callback; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return isCompleted; }
            internal set { isCompleted = value; }
        }

        internal Exception SavedException
        {
            get { return this.savedException; }
            set { this.savedException = value; }
        }

        #endregion Properties


        #region Constructors

        public TorrentCreatorAsyncResult(object asyncState, AsyncCallback callback)
        {
            this.asyncState = asyncState;
            this.callback = callback;
            this.waitHandle = new ManualResetEvent(false);
        }

        #endregion Constructors


        #region Methods

        public void Abort()
        {
            this.aborted = true;
        }

        #endregion Methods
    }


    ///<summary>
    ///this class is used to create on disk torrent files.
    ///</summary>
    public class TorrentCreator
    {
        #region Events

        public event EventHandler<TorrentCreatorEventArgs> Hashed;

        #endregion


        #region Private Fields

        private List<MonoTorrentCollection<string>> announces;      // The list of announce urls
        private bool ignoreHiddenFiles;                             // True if you want to ignore hidden files when making the torrent
        private string path;                                        // The path from which the torrent will be created (can be file or directory)
        private TorrentCreatorAsyncResult result;                   // The IAsyncResult generated by creating the torrent
        private bool storeMd5;                                      // True if an MD5 hash of each file should be included
        private BEncodedDictionary torrent;                         // The BencodedDictionary which contains the data to be written to the .torrent file

        #endregion Private Fields


        #region Properties

        /// <summary>
        /// The list of announce urls for this 
        /// </summary>
        public List<MonoTorrentCollection<string>> Announces
        {
            get { return this.announces; }
        }


        ///<summary>
        ///this property sets the comment entry in the torrent file. default is string.Empty to conserve bandwidth.
        ///</summary>
        public string Comment
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("comment"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "comment", new BEncodedString(value)); }
        }


        ///<summary>this property sets the created by entrie in the torrent file
        ///</summary>
        public string CreatedBy
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("created by"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "created by", new BEncodedString(value)); }
        }


        /// <summary>
        ///  The encoding used to create the torrent
        /// </summary>
        public string Encoding
        {
            get { return Get(this.torrent, new BEncodedString("encoding")).ToString(); }
        }


        /// <summary>
        /// The path from which you would like to make a torrent of.
        ///Path can be a file and a directory. 
        /// </summary>
        public string Path
        {
            get { return path; }
            set { path = value; }

        }


        ///<summary>
        ///this property sets the length of the pieces. default is 512 kb.
        ///</summary>
        public long PieceLength
        {
            get
            {
                BEncodedValue val = Get((BEncodedDictionary)this.torrent["info"], new BEncodedString("piece length"));
                return val == null ? -1 : ((BEncodedNumber)val).Number;
            }
            set { Set((BEncodedDictionary)this.torrent["info"], "piece length", new BEncodedNumber(value)); }
        }


        ///<summary>
        ///This property sets whether this is a private torrent or not
        ///</summary>
        public bool Private
        {
            get
            {
                BEncodedValue val = Get((BEncodedDictionary)this.torrent["info"], new BEncodedString("private"));
                return val == null ? false : ((BEncodedNumber)val).Number == 1;
            }
            set { Set((BEncodedDictionary)this.torrent["info"], "private", new BEncodedNumber(value ? 1 : 0)); }
        }


        public string Publisher
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("publisher"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "publisher", new BEncodedString(value)); }
        }


        ///<summary>this property sets the Publisher Url entry
        ///</summary>
        public string PublisherUrl
        {
            get
            {
                BEncodedValue val = Get(this.torrent, new BEncodedString("publisher-url"));
                return val == null ? string.Empty : val.ToString();
            }
            set { Set(this.torrent, "publisher-url", new BEncodedString(value)); }
        }


        ///<summary>this property sets wheather the optional field md5sum should be included. 
        ///default is false to conserve bandwidth
        ///</summary>
        public bool StoreMD5
        {
            get { return storeMd5; }
            set { storeMd5 = value; }
        }


        public bool IgnoreHiddenFiles
        {
            get { return ignoreHiddenFiles; }
            set { ignoreHiddenFiles = value; }
        }

        #endregion Properties


        #region Constructors

        public TorrentCreator()
        {
            BEncodedDictionary info = new BEncodedDictionary();
            this.announces = new List<MonoTorrentCollection<string>>();
            this.ignoreHiddenFiles = true;
            this.torrent = new BEncodedDictionary();
            this.torrent.Add("info", info);

            // Add in initial values for some of the torrent attributes
            PieceLength =  256 * 1024;   // 256kB default piece size
            torrent.Add("encoding", (BEncodedString)"UTF-8");
        }

        #endregion Constructors


        #region Methods

        ///<summary>
        ///this method runs recursively through all subdirs under dir and ads information
        ///from each file to the filesList. 
        ///<summary>
        ///<param name="dir">the top directory to start from</param>
        ///<param name="filesList">the list to store the file information</param>
        ///<param name="paths">a list of all files found. used for piece hashing later</param>
        private void AddAllFileInfoDicts(string dir, BEncodedList filesList, MonoTorrentCollection<string> paths)
        {
            GetAllFilePaths(dir, paths);

            for(int i=0; i < paths.Count; i++)
                filesList.Add(GetFileInfoDict(paths[i], dir));
        }


        private void GetAllFilePaths(string directory, MonoTorrentCollection<string> paths)
        {
            string[] subs = Directory.GetFileSystemEntries(directory);
            foreach (string path in subs)
            {
                if (Directory.Exists(path))
                {
                    if (ignoreHiddenFiles)
                    {
                        DirectoryInfo info = new DirectoryInfo(path);
                        if ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                            continue;
                    }

                    GetAllFilePaths(path, paths);
                }
                else
                {
                    if (ignoreHiddenFiles)
                    {
                        FileInfo info = new FileInfo(path);
                        if ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                            continue;
                    }

                    paths.Add(path);
                }
            }
        }


        ///<summary>
        ///this adds stuff common to single and multi file torrents
        ///</summary>
        private void AddCommonStuff(BEncodedDictionary torrent)
        {
            Debug.WriteLine(announces[0][0]);
            torrent.Add("announce", new BEncodedString(announces[0][0]));

            // If there is more than one tier or the first tier has more than 1 tracker
            if (announces.Count > 1 || announces[0].Count > 0)
            {
                BEncodedList announceList = new BEncodedList();
                for (int i = 0; i < this.announces.Count; i++)
                {
                    BEncodedList tier = new BEncodedList();
                    for (int j = 0; j < this.announces[i].Count; j++)
                        tier.Add(new BEncodedString(this.announces[i][j]));

                    announceList.Add(tier);
                }

                torrent.Add("announce-list", announceList);
            }


            DateTime epocheStart = new DateTime(1970, 1, 1);
            TimeSpan span = DateTime.Now - epocheStart;
            Debug.WriteLine("creation date: " + DateTime.Now.ToString() + " - " + epocheStart.ToString() + " = " + span.ToString() + " : " + (long)span.TotalSeconds);
            torrent.Add("creation date", new BEncodedNumber((long)span.TotalSeconds));
        }


        /// <summary>
        /// This can be used to add custom values to the main bencoded dictionary
        /// </summary>        
        public void AddCustom(BEncodedString key, BEncodedValue value)
        {
            this.torrent.Add(key, value);
        }


        /// <summary>
        /// This can be used to add custom values to the main bencoded dictionary
        /// </summary>        
        public void AddCustom(KeyValuePair<BEncodedString, BEncodedValue> customInfo)
        {
            this.torrent.Add(customInfo);
        }


        ///<summary>calculate md5sum of a file</summary>
        ///<param name="fileName">the file to sum with md5</param>
        private void AddMD5(BEncodedDictionary dict, string fileName)
        {
            MD5 hasher = MD5.Create();
            StringBuilder sb = new StringBuilder();

            using (FileStream stream = new FileStream(fileName, FileMode.Open))
            {
                byte[] hash = hasher.ComputeHash(stream);

                foreach (byte b in hash)
                {
                    string hex = b.ToString("X");
                    hex = hex.Length > 1 ? hex : "0" + hex;
                    sb.Append(hex);
                }
                Console.WriteLine("sum for file " + fileName + " = " + sb.ToString());
            }
            dict.Add("md5sum", new BEncodedString(sb.ToString()));
        }


        public TorrentCreatorAsyncResult BeginCreate(object asyncState, AsyncCallback callback)
        {
            if (result != null)
                throw new TorrentException("You must call EndCreate before calling BeginCreate again");

            result = new TorrentCreatorAsyncResult(asyncState, callback);

            // Start the processing in a seperate thread, a user thread.
            new Thread(new ThreadStart(AsyncCreate)).Start();
            return result;
        }

        public BEncodedDictionary EndCreate(IAsyncResult result)
        {

            if (result != this.result)
                throw new ArgumentException("The supplied async result does not correspond to currently active async result");
            try
            {
                if (this.result.SavedException != null)
                    throw this.result.SavedException;

                return this.torrent;
            }
            finally
            {
                this.result = null;
            }
        }

        private void AsyncCreate()
        {
            try
            {
                Create();
            }
            catch (Exception ex)
            {
                result.SavedException = ex;
            }
            
            result.IsCompleted = true;
            ((ManualResetEvent)result.AsyncWaitHandle).Set();

            if (result.Callback != null)
                result.Callback(this.result);
        }

        ///<summary>
        ///calculates all hashes over the files which should be included in the torrent
        ///</summmary>
        private byte[] CalcPiecesHash(MonoTorrentCollection<string> fullPaths)
        {
            SHA1 hasher = SHA1.Create();
            byte[] piece = new byte[PieceLength];//holds one piece for sha1 calcing
            int len = 0;        //holds the bytes read by the stream.Read() method
            byte[] piecesBuffer = new byte[GetPieceCount(fullPaths) * 20]; //holds all the pieces hashes
            int piecesBufferOffset = 0;
{'fraction_non_alphanumeric': 0.08924470321850235, 'fraction_numerical': 0.004140384926411127, 'mean_word_length': 1.6289115646258503, 'pattern_counts': {'":': 0, '<': 123, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1873374', 'n_tokens_mistral': 7840, 'n_tokens_neox': 7355, 'n_words': 2363}