mirror of https://github.com/Radarr/Radarr
New: Update MonoTorrent from nuget
This commit is contained in:
parent
0845a4bf4c
commit
6f115d2db3
|
@ -1,320 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded Dictionary
|
||||
/// </summary>
|
||||
public class BEncodedDictionary : BEncodedValue, IDictionary<BEncodedString, BEncodedValue>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private SortedDictionary<BEncodedString, BEncodedValue> dictionary;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedDictionary
|
||||
/// </summary>
|
||||
public BEncodedDictionary()
|
||||
{
|
||||
this.dictionary = new SortedDictionary<BEncodedString, BEncodedValue>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the dictionary to a byte[]
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the data to</param>
|
||||
/// <param name="offset">The offset to start writing the data to</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = 0;
|
||||
|
||||
//Dictionaries start with 'd'
|
||||
buffer[offset] = (byte)'d';
|
||||
written++;
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this)
|
||||
{
|
||||
written += keypair.Key.Encode(buffer, offset + written);
|
||||
written += keypair.Value.Encode(buffer, offset + written);
|
||||
}
|
||||
|
||||
// Dictionaries end with 'e'
|
||||
buffer[offset + written] = (byte)'e';
|
||||
written++;
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
DecodeInternal(reader, reader.StrictDecoding);
|
||||
}
|
||||
|
||||
private void DecodeInternal(RawReader reader, bool strictDecoding)
|
||||
{
|
||||
BEncodedString key = null;
|
||||
BEncodedValue value = null;
|
||||
BEncodedString oldkey = null;
|
||||
|
||||
if (reader.ReadByte() != 'd')
|
||||
throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd'
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
{
|
||||
key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings
|
||||
|
||||
if (oldkey != null && oldkey.CompareTo(key) > 0)
|
||||
if (strictDecoding)
|
||||
throw new BEncodingException(String.Format(
|
||||
"Illegal BEncodedDictionary. The attributes are not ordered correctly. Old key: {0}, New key: {1}",
|
||||
oldkey, key));
|
||||
|
||||
oldkey = key;
|
||||
value = BEncodedValue.Decode(reader); // the value is a BEncoded value
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
if (reader.ReadByte() != 'e') // remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
}
|
||||
|
||||
public static BEncodedDictionary DecodeTorrent(byte[] bytes)
|
||||
{
|
||||
return DecodeTorrent(new MemoryStream(bytes));
|
||||
}
|
||||
|
||||
public static BEncodedDictionary DecodeTorrent(Stream s)
|
||||
{
|
||||
return DecodeTorrent(new RawReader(s));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Special decoding method for torrent files - allows dictionary attributes to be out of order for the
|
||||
/// overall torrent file, but imposes strict rules on the info dictionary.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static BEncodedDictionary DecodeTorrent(RawReader reader)
|
||||
{
|
||||
BEncodedString key = null;
|
||||
BEncodedValue value = null;
|
||||
BEncodedDictionary torrent = new BEncodedDictionary();
|
||||
if (reader.ReadByte() != 'd')
|
||||
throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd'
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
{
|
||||
key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings
|
||||
|
||||
if (reader.PeekByte() == 'd')
|
||||
{
|
||||
value = new BEncodedDictionary();
|
||||
if (key.Text.ToLower().Equals("info"))
|
||||
((BEncodedDictionary)value).DecodeInternal(reader, true);
|
||||
else
|
||||
((BEncodedDictionary)value).DecodeInternal(reader, false);
|
||||
}
|
||||
else
|
||||
value = BEncodedValue.Decode(reader); // the value is a BEncoded value
|
||||
|
||||
torrent.dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
if (reader.ReadByte() != 'e') // remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the dictionary in bytes using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
int length = 0;
|
||||
length += 1; // Dictionaries start with 'd'
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this.dictionary)
|
||||
{
|
||||
length += keypair.Key.LengthInBytes();
|
||||
length += keypair.Value.LengthInBytes();
|
||||
}
|
||||
length += 1; // Dictionaries end with 'e'
|
||||
return length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedValue val;
|
||||
BEncodedDictionary other = obj as BEncodedDictionary;
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (this.dictionary.Count != other.dictionary.Count)
|
||||
return false;
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this.dictionary)
|
||||
{
|
||||
if (!other.TryGetValue(keypair.Key, out val))
|
||||
return false;
|
||||
|
||||
if (!keypair.Value.Equals(val))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int result = 0;
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in dictionary)
|
||||
{
|
||||
result ^= keypair.Key.GetHashCode();
|
||||
result ^= keypair.Value.GetHashCode();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(Encode());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDictionary and IList methods
|
||||
public void Add(BEncodedString key, BEncodedValue value)
|
||||
{
|
||||
this.dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
this.dictionary.Add(item.Key, item.Value);
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
this.dictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
if (!this.dictionary.ContainsKey(item.Key))
|
||||
return false;
|
||||
|
||||
return this.dictionary[item.Key].Equals(item.Value);
|
||||
}
|
||||
|
||||
public bool ContainsKey(BEncodedString key)
|
||||
{
|
||||
return this.dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<BEncodedString, BEncodedValue>[] array, int arrayIndex)
|
||||
{
|
||||
this.dictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return this.dictionary.Count; }
|
||||
}
|
||||
|
||||
//public int IndexOf(KeyValuePair<BEncodedString, IBEncodedValue> item)
|
||||
//{
|
||||
// return this.dictionary.IndexOf(item);
|
||||
//}
|
||||
|
||||
//public void Insert(int index, KeyValuePair<BEncodedString, IBEncodedValue> item)
|
||||
//{
|
||||
// this.dictionary.Insert(index, item);
|
||||
//}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Remove(BEncodedString key)
|
||||
{
|
||||
return this.dictionary.Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
return this.dictionary.Remove(item.Key);
|
||||
}
|
||||
|
||||
//public void RemoveAt(int index)
|
||||
//{
|
||||
// this.dictionary.RemoveAt(index);
|
||||
//}
|
||||
|
||||
public bool TryGetValue(BEncodedString key, out BEncodedValue value)
|
||||
{
|
||||
return this.dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public BEncodedValue this[BEncodedString key]
|
||||
{
|
||||
get { return this.dictionary[key]; }
|
||||
set { this.dictionary[key] = value; }
|
||||
}
|
||||
|
||||
//public KeyValuePair<BEncodedString, IBEncodedValue> this[int index]
|
||||
//{
|
||||
// get { return this.dictionary[index]; }
|
||||
// set { this.dictionary[index] = value; }
|
||||
//}
|
||||
|
||||
public ICollection<BEncodedString> Keys
|
||||
{
|
||||
get { return this.dictionary.Keys; }
|
||||
}
|
||||
|
||||
public ICollection<BEncodedValue> Values
|
||||
{
|
||||
get { return this.dictionary.Values; }
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<BEncodedString, BEncodedValue>> GetEnumerator()
|
||||
{
|
||||
return this.dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.dictionary.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded list
|
||||
/// </summary>
|
||||
public class BEncodedList : BEncodedValue, IList<BEncodedValue>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private List<BEncodedValue> list;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Create a new BEncoded List with default capacity
|
||||
/// </summary>
|
||||
public BEncodedList()
|
||||
: this(new List<BEncodedValue>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncoded List with the supplied capacity
|
||||
/// </summary>
|
||||
/// <param name="capacity">The initial capacity</param>
|
||||
public BEncodedList(int capacity)
|
||||
: this(new List<BEncodedValue>(capacity))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BEncodedList(IEnumerable<BEncodedValue> list)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException("list");
|
||||
|
||||
this.list = new List<BEncodedValue>(list);
|
||||
}
|
||||
|
||||
private BEncodedList(List<BEncodedValue> value)
|
||||
{
|
||||
this.list = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the list to a byte[]
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the list to</param>
|
||||
/// <param name="offset">The offset to start writing the data at</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = 0;
|
||||
buffer[offset] = (byte)'l';
|
||||
written++;
|
||||
for (int i = 0; i < this.list.Count; i++)
|
||||
written += this.list[i].Encode(buffer, offset + written);
|
||||
buffer[offset + written] = (byte)'e';
|
||||
written++;
|
||||
return written;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncodedList from the given StreamReader
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
if (reader.ReadByte() != 'l') // Remove the leading 'l'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
list.Add(BEncodedValue.Decode(reader));
|
||||
|
||||
if (reader.ReadByte() != 'e') // Remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// Returns the size of the list in bytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
length += 1; // Lists start with 'l'
|
||||
for (int i = 0; i < this.list.Count; i++)
|
||||
length += this.list[i].LengthInBytes();
|
||||
|
||||
length += 1; // Lists end with 'e'
|
||||
return length;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedList other = obj as BEncodedList;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < this.list.Count; i++)
|
||||
if (!this.list[i].Equals(other.list[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
result ^= list[i].GetHashCode();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(Encode());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IList methods
|
||||
public void Add(BEncodedValue item)
|
||||
{
|
||||
this.list.Add(item);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<BEncodedValue> collection)
|
||||
{
|
||||
list.AddRange(collection);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.list.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(BEncodedValue item)
|
||||
{
|
||||
return this.list.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(BEncodedValue[] array, int arrayIndex)
|
||||
{
|
||||
this.list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return this.list.Count; }
|
||||
}
|
||||
|
||||
public int IndexOf(BEncodedValue item)
|
||||
{
|
||||
return this.list.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, BEncodedValue item)
|
||||
{
|
||||
this.list.Insert(index, item);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Remove(BEncodedValue item)
|
||||
{
|
||||
return this.list.Remove(item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
this.list.RemoveAt(index);
|
||||
}
|
||||
|
||||
public BEncodedValue this[int index]
|
||||
{
|
||||
get { return this.list[index]; }
|
||||
set { this.list[index] = value; }
|
||||
}
|
||||
|
||||
public IEnumerator<BEncodedValue> GetEnumerator()
|
||||
{
|
||||
return this.list.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded number
|
||||
/// </summary>
|
||||
public class BEncodedNumber : BEncodedValue, IComparable<BEncodedNumber>
|
||||
{
|
||||
#region Member Variables
|
||||
/// <summary>
|
||||
/// The value of the BEncodedNumber
|
||||
/// </summary>
|
||||
public long Number
|
||||
{
|
||||
get { return number; }
|
||||
set { number = value; }
|
||||
}
|
||||
internal long number;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public BEncodedNumber()
|
||||
: this(0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncoded number with the given value
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The inital value of the BEncodedNumber</param>
|
||||
public BEncodedNumber(long value)
|
||||
{
|
||||
this.number = value;
|
||||
}
|
||||
|
||||
public static implicit operator BEncodedNumber(long value)
|
||||
{
|
||||
return new BEncodedNumber(value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
/// <summary>
|
||||
/// Encodes this number to the supplied byte[] starting at the supplied offset
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to write the data to</param>
|
||||
/// <param name="offset">The offset to start writing the data at</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
long number = this.number;
|
||||
|
||||
int written = offset;
|
||||
buffer[written++] = (byte)'i';
|
||||
|
||||
if (number < 0)
|
||||
{
|
||||
buffer[written++] = (byte)'-';
|
||||
number = -number;
|
||||
}
|
||||
// Reverse the number '12345' to get '54321'
|
||||
long reversed = 0;
|
||||
for (long i = number; i != 0; i /= 10)
|
||||
reversed = reversed * 10 + i % 10;
|
||||
|
||||
// Write each digit of the reversed number to the array. We write '1'
|
||||
// first, then '2', etc
|
||||
for (long i = reversed; i != 0; i /= 10)
|
||||
buffer[written++] = (byte)(i % 10 + '0');
|
||||
|
||||
if (number == 0)
|
||||
buffer[written++] = (byte)'0';
|
||||
|
||||
// If the original number ends in one or more zeros, they are lost
|
||||
// when we reverse the number. We add them back in here.
|
||||
for (long i = number; i % 10 == 0 && number != 0; i /= 10)
|
||||
buffer[written++] = (byte)'0';
|
||||
|
||||
buffer[written++] = (byte)'e';
|
||||
return written - offset;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncoded number from the supplied RawReader
|
||||
/// </summary>
|
||||
/// <param name="reader">RawReader containing a BEncoded Number</param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
int sign = 1;
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
if (reader.ReadByte() != 'i') // remove the leading 'i'
|
||||
throw new BEncodingException("Invalid data found. Aborting.");
|
||||
|
||||
if (reader.PeekByte() == '-')
|
||||
{
|
||||
sign = -1;
|
||||
reader.ReadByte();
|
||||
}
|
||||
|
||||
int letter;
|
||||
while (((letter = reader.PeekByte()) != -1) && letter != 'e')
|
||||
{
|
||||
if (letter < '0' || letter > '9')
|
||||
throw new BEncodingException("Invalid number found.");
|
||||
number = number * 10 + (letter - '0');
|
||||
reader.ReadByte();
|
||||
}
|
||||
if (reader.ReadByte() != 'e') //remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting.");
|
||||
|
||||
number *= sign;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// Returns the length of the encoded string in bytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
long number = this.number;
|
||||
int count = 2; // account for the 'i' and 'e'
|
||||
|
||||
if (number == 0)
|
||||
return count + 1;
|
||||
|
||||
if (number < 0)
|
||||
{
|
||||
number = -number;
|
||||
count++;
|
||||
}
|
||||
for (long i = number; i != 0; i /= 10)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
if (other is BEncodedNumber || other is long || other is int)
|
||||
return CompareTo((BEncodedNumber)other);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int CompareTo(BEncodedNumber other)
|
||||
{
|
||||
if (other == null)
|
||||
throw new ArgumentNullException("other");
|
||||
|
||||
return this.number.CompareTo(other.number);
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(long other)
|
||||
{
|
||||
return this.number.CompareTo(other);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedNumber obj2 = obj as BEncodedNumber;
|
||||
if (obj2 == null)
|
||||
return false;
|
||||
|
||||
return (this.number == obj2.number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.number.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return (this.number.ToString());
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using MonoTorrent.Messages;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded string
|
||||
/// </summary>
|
||||
public class BEncodedString : BEncodedValue, IComparable<BEncodedString>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The value of the BEncodedString
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return Encoding.UTF8.GetString(textBytes); }
|
||||
set { textBytes = Encoding.UTF8.GetBytes(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The underlying byte[] associated with this BEncodedString
|
||||
/// </summary>
|
||||
public byte[] TextBytes
|
||||
{
|
||||
get { return this.textBytes; }
|
||||
}
|
||||
private byte[] textBytes;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
public BEncodedString()
|
||||
: this(new byte[0])
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public BEncodedString(char[] value)
|
||||
: this(System.Text.Encoding.UTF8.GetBytes(value))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value">Initial value for the string</param>
|
||||
public BEncodedString(string value)
|
||||
: this(System.Text.Encoding.UTF8.GetBytes(value))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public BEncodedString(byte[] value)
|
||||
{
|
||||
this.textBytes = value;
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator BEncodedString(string value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
public static implicit operator BEncodedString(char[] value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
public static implicit operator BEncodedString(byte[] value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedString to a byte[] using the supplied Encoding
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the string to</param>
|
||||
/// <param name="offset">The offset at which to save the data to</param>
|
||||
/// <param name="e">The encoding to use</param>
|
||||
/// <returns>The number of bytes encoded</returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = offset;
|
||||
written += Message.WriteAscii(buffer, written, textBytes.Length.ToString());
|
||||
written += Message.WriteAscii(buffer, written, ":");
|
||||
written += Message.Write(buffer, written, textBytes);
|
||||
return written - offset;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncodedString from the supplied StreamReader
|
||||
/// </summary>
|
||||
/// <param name="reader">The StreamReader containing the BEncodedString</param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
int letterCount;
|
||||
string length = string.Empty;
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != ':')) // read in how many characters
|
||||
length += (char)reader.ReadByte(); // the string is
|
||||
|
||||
if (reader.ReadByte() != ':') // remove the ':'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
if (!int.TryParse(length, out letterCount))
|
||||
throw new BEncodingException(string.Format("Invalid BEncodedString. Length was '{0}' instead of a number", length));
|
||||
|
||||
this.textBytes = new byte[letterCount];
|
||||
if (reader.Read(textBytes, 0, letterCount) != letterCount)
|
||||
throw new BEncodingException("Couldn't decode string");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
public string Hex
|
||||
{
|
||||
get { return BitConverter.ToString(TextBytes); }
|
||||
}
|
||||
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
// The length is equal to the length-prefix + ':' + length of data
|
||||
int prefix = 1; // Account for ':'
|
||||
|
||||
// Count the number of characters needed for the length prefix
|
||||
for (int i = textBytes.Length; i != 0; i = i / 10)
|
||||
prefix += 1;
|
||||
|
||||
if (textBytes.Length == 0)
|
||||
prefix++;
|
||||
|
||||
return prefix + textBytes.Length;
|
||||
}
|
||||
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
return CompareTo(other as BEncodedString);
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(BEncodedString other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
|
||||
int difference = 0;
|
||||
int length = this.textBytes.Length > other.textBytes.Length ? other.textBytes.Length : this.textBytes.Length;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
if ((difference = this.textBytes[i].CompareTo(other.textBytes[i])) != 0)
|
||||
return difference;
|
||||
|
||||
if (this.textBytes.Length == other.textBytes.Length)
|
||||
return 0;
|
||||
|
||||
return this.textBytes.Length > other.textBytes.Length ? 1 : -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
BEncodedString other;
|
||||
if (obj is string)
|
||||
other = new BEncodedString((string)obj);
|
||||
else if (obj is BEncodedString)
|
||||
other = (BEncodedString)obj;
|
||||
else
|
||||
return false;
|
||||
|
||||
return Toolbox.ByteMatch(this.textBytes, other.textBytes);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
for (int i = 0; i < this.textBytes.Length; i++)
|
||||
hash += this.textBytes[i];
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(textBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
[Serializable]
|
||||
public class BEncodingException : Exception
|
||||
{
|
||||
public BEncodingException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public BEncodingException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BEncodingException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected BEncodingException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for all BEncoded values.
|
||||
/// </summary>
|
||||
public abstract class BEncodedValue
|
||||
{
|
||||
internal abstract void DecodeInternal(RawReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedValue into a byte array
|
||||
/// </summary>
|
||||
/// <returns>Byte array containing the BEncoded Data</returns>
|
||||
public byte[] Encode()
|
||||
{
|
||||
byte[] buffer = new byte[LengthInBytes()];
|
||||
if (Encode(buffer, 0) != buffer.Length)
|
||||
throw new BEncodingException("Error encoding the data");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedValue into the supplied buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the information to</param>
|
||||
/// <param name="offset">The offset in the buffer to start writing the data</param>
|
||||
/// <returns></returns>
|
||||
public abstract int Encode(byte[] buffer, int offset);
|
||||
|
||||
public static T Clone<T>(T value)
|
||||
where T : BEncodedValue
|
||||
{
|
||||
Check.Value(value);
|
||||
return (T)BEncodedValue.Decode(value.Encode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all BEncoded values
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the BEncoded data</param>
|
||||
/// <returns></returns>
|
||||
public static BEncodedValue Decode(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException("data");
|
||||
|
||||
using (RawReader stream = new RawReader(new MemoryStream(data)))
|
||||
return (Decode(stream));
|
||||
}
|
||||
|
||||
internal static BEncodedValue Decode(byte[] buffer, bool strictDecoding)
|
||||
{
|
||||
return Decode(buffer, 0, buffer.Length, strictDecoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given byte array
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing the BEncoded data</param>
|
||||
/// <param name="offset">The offset at which the data starts at</param>
|
||||
/// <param name="length">The number of bytes to be decoded</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the byte[]</returns>
|
||||
public static BEncodedValue Decode(byte[] buffer, int offset, int length)
|
||||
{
|
||||
return Decode(buffer, offset, length, true);
|
||||
}
|
||||
|
||||
public static BEncodedValue Decode(byte[] buffer, int offset, int length, bool strictDecoding)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || length < 0)
|
||||
throw new IndexOutOfRangeException("Neither offset or length can be less than zero");
|
||||
|
||||
if (offset > buffer.Length - length)
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
|
||||
using (RawReader reader = new RawReader(new MemoryStream(buffer, offset, length), strictDecoding))
|
||||
return (BEncodedValue.Decode(reader));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given stream
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static BEncodedValue Decode(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
return Decode(new RawReader(stream, false));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given RawReader
|
||||
/// </summary>
|
||||
/// <param name="reader">The RawReader containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static BEncodedValue Decode(RawReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
BEncodedValue data;
|
||||
switch (reader.PeekByte())
|
||||
{
|
||||
case ('i'): // Integer
|
||||
data = new BEncodedNumber();
|
||||
break;
|
||||
|
||||
case ('d'): // Dictionary
|
||||
data = new BEncodedDictionary();
|
||||
break;
|
||||
|
||||
case ('l'): // List
|
||||
data = new BEncodedList();
|
||||
break;
|
||||
|
||||
case ('1'): // String
|
||||
case ('2'):
|
||||
case ('3'):
|
||||
case ('4'):
|
||||
case ('5'):
|
||||
case ('6'):
|
||||
case ('7'):
|
||||
case ('8'):
|
||||
case ('9'):
|
||||
case ('0'):
|
||||
data = new BEncodedString();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BEncodingException("Could not find what value to decode");
|
||||
}
|
||||
|
||||
data.DecodeInternal(reader);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all BEncoded values
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the BEncoded data</param>
|
||||
/// <returns></returns>
|
||||
public static T Decode<T>(byte[] data) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(data);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given byte array
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing the BEncoded data</param>
|
||||
/// <param name="offset">The offset at which the data starts at</param>
|
||||
/// <param name="length">The number of bytes to be decoded</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the byte[]</returns>
|
||||
public static T Decode<T>(byte[] buffer, int offset, int length) where T : BEncodedValue
|
||||
{
|
||||
return BEncodedValue.Decode<T>(buffer, offset, length, true);
|
||||
}
|
||||
|
||||
public static T Decode<T>(byte[] buffer, int offset, int length, bool strictDecoding) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(buffer, offset, length, strictDecoding);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given stream
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static T Decode<T>(Stream stream) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(stream);
|
||||
}
|
||||
|
||||
|
||||
public static T Decode<T>(RawReader reader) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(reader);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the byte[] needed to encode this BEncodedValue
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract int LengthInBytes();
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
public class RawReader : Stream
|
||||
{
|
||||
bool hasPeek;
|
||||
Stream input;
|
||||
byte[] peeked;
|
||||
bool strictDecoding;
|
||||
|
||||
public bool StrictDecoding
|
||||
{
|
||||
get { return strictDecoding; }
|
||||
}
|
||||
|
||||
public RawReader(Stream input)
|
||||
: this(input, true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RawReader(Stream input, bool strictDecoding)
|
||||
{
|
||||
this.input = input;
|
||||
this.peeked = new byte[1];
|
||||
this.strictDecoding = strictDecoding;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return input.CanRead; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return input.CanSeek; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { return input.Length; }
|
||||
}
|
||||
|
||||
public int PeekByte()
|
||||
{
|
||||
if (!hasPeek)
|
||||
hasPeek = Read(peeked, 0, 1) == 1;
|
||||
return hasPeek ? peeked[0] : -1;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (hasPeek)
|
||||
{
|
||||
hasPeek = false;
|
||||
return peeked[0];
|
||||
}
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hasPeek)
|
||||
return input.Position - 1;
|
||||
return input.Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != Position)
|
||||
{
|
||||
hasPeek = false;
|
||||
input.Position = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = 0;
|
||||
if (hasPeek && count > 0)
|
||||
{
|
||||
hasPeek = false;
|
||||
buffer[offset] = peeked[0];
|
||||
offset++;
|
||||
count--;
|
||||
read++;
|
||||
}
|
||||
read += input.Read(buffer, offset, count);
|
||||
return read;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long val;
|
||||
if (hasPeek && origin == SeekOrigin.Current)
|
||||
val = input.Seek(offset - 1, origin);
|
||||
else
|
||||
val = input.Seek(offset, origin);
|
||||
hasPeek = false;
|
||||
return val;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,420 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is for represting the Peer's bitfield
|
||||
/// </summary>
|
||||
public class BitField : ICloneable, IEnumerable<bool>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private int[] array;
|
||||
private int length;
|
||||
private int trueCount;
|
||||
|
||||
internal bool AllFalse
|
||||
{
|
||||
get { return this.trueCount == 0; }
|
||||
}
|
||||
|
||||
internal bool AllTrue
|
||||
{
|
||||
get { return this.trueCount == this.length; }
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return this.length; }
|
||||
}
|
||||
|
||||
public double PercentComplete
|
||||
{
|
||||
get { return (double)this.trueCount / this.length * 100.0; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public BitField(byte[] array, int length)
|
||||
: this(length)
|
||||
{
|
||||
this.FromArray(array, 0, array.Length);
|
||||
}
|
||||
|
||||
public BitField(int length)
|
||||
{
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
|
||||
this.length = length;
|
||||
this.array = new int[(length + 31) / 32];
|
||||
}
|
||||
|
||||
public BitField(bool[] array)
|
||||
{
|
||||
this.length = array.Length;
|
||||
this.array = new int[(array.Length + 31) / 32];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
this.Set(i, array[i]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods BitArray
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
get { return this.Get(index); }
|
||||
internal set { this.Set(index, value); }
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return this.Clone();
|
||||
}
|
||||
|
||||
public BitField Clone()
|
||||
{
|
||||
BitField b = new BitField(this.length);
|
||||
Buffer.BlockCopy(this.array, 0, b.array, 0, this.array.Length * 4);
|
||||
b.trueCount = this.trueCount;
|
||||
return b;
|
||||
}
|
||||
|
||||
public BitField From(BitField value)
|
||||
{
|
||||
this.Check(value);
|
||||
Buffer.BlockCopy(value.array, 0, this.array, 0, this.array.Length * 4);
|
||||
this.trueCount = value.trueCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitField Not()
|
||||
{
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] = ~this.array[i];
|
||||
|
||||
this.trueCount = this.length - this.trueCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitField And(BitField value)
|
||||
{
|
||||
this.Check(value);
|
||||
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] &= value.array[i];
|
||||
|
||||
this.Validate();
|
||||
return this;
|
||||
}
|
||||
|
||||
internal BitField NAnd(BitField value)
|
||||
{
|
||||
this.Check(value);
|
||||
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] &= ~value.array[i];
|
||||
|
||||
this.Validate();
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitField Or(BitField value)
|
||||
{
|
||||
this.Check(value);
|
||||
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] |= value.array[i];
|
||||
|
||||
this.Validate();
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitField Xor(BitField value)
|
||||
{
|
||||
this.Check(value);
|
||||
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] ^= value.array[i];
|
||||
|
||||
this.Validate();
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BitField bf = obj as BitField;
|
||||
|
||||
if (bf == null || this.array.Length != bf.array.Length || this.TrueCount != bf.TrueCount)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
if (this.array[i] != bf.array[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int FirstTrue()
|
||||
{
|
||||
return this.FirstTrue(0, this.length);
|
||||
}
|
||||
|
||||
public int FirstTrue(int startIndex, int endIndex)
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
|
||||
// If the number of pieces is an exact multiple of 32, we need to decrement by 1 so we don't overrun the array
|
||||
// For the case when endIndex == 0, we need to ensure we don't go negative
|
||||
int loopEnd = Math.Min((endIndex / 32), this.array.Length - 1);
|
||||
for (int i = (startIndex / 32); i <= loopEnd; i++)
|
||||
{
|
||||
if (this.array[i] == 0) // This one has no true values
|
||||
continue;
|
||||
|
||||
start = i * 32;
|
||||
end = start + 32;
|
||||
start = (start < startIndex) ? startIndex : start;
|
||||
end = (end > this.length) ? this.length : end;
|
||||
end = (end > endIndex) ? endIndex : end;
|
||||
if (end == this.Length && end > 0)
|
||||
end--;
|
||||
|
||||
for (int j = start; j <= end; j++)
|
||||
if (this.Get(j)) // This piece is true
|
||||
return j;
|
||||
}
|
||||
|
||||
return -1; // Nothing is true
|
||||
}
|
||||
|
||||
public int FirstFalse()
|
||||
{
|
||||
return this.FirstFalse(0, this.Length);
|
||||
}
|
||||
|
||||
public int FirstFalse(int startIndex, int endIndex)
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
|
||||
// If the number of pieces is an exact multiple of 32, we need to decrement by 1 so we don't overrun the array
|
||||
// For the case when endIndex == 0, we need to ensure we don't go negative
|
||||
int loopEnd = Math.Min((endIndex / 32), this.array.Length - 1);
|
||||
for (int i = (startIndex / 32); i <= loopEnd; i++)
|
||||
{
|
||||
if (this.array[i] == ~0) // This one has no false values
|
||||
continue;
|
||||
|
||||
start = i * 32;
|
||||
end = start + 32;
|
||||
start = (start < startIndex) ? startIndex : start;
|
||||
end = (end > this.length) ? this.length : end;
|
||||
end = (end > endIndex) ? endIndex : end;
|
||||
if (end == this.Length && end > 0)
|
||||
end--;
|
||||
|
||||
for (int j = start; j <= end; j++)
|
||||
if (!this.Get(j)) // This piece is true
|
||||
return j;
|
||||
}
|
||||
|
||||
return -1; // Nothing is true
|
||||
}
|
||||
internal void FromArray(byte[] buffer, int offset, int length)
|
||||
{
|
||||
int end = this.Length / 32;
|
||||
for (int i = 0; i < end; i++)
|
||||
this.array[i] = (buffer[offset++] << 24) |
|
||||
(buffer[offset++] << 16) |
|
||||
(buffer[offset++] << 8) |
|
||||
(buffer[offset++] << 0);
|
||||
|
||||
int shift = 24;
|
||||
for (int i = end * 32; i < this.Length; i += 8)
|
||||
{
|
||||
this.array[this.array.Length - 1] |= buffer[offset++] << shift;
|
||||
shift -= 8;
|
||||
}
|
||||
this.Validate();
|
||||
}
|
||||
|
||||
bool Get(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.length)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return (this.array[index >> 5] & (1 << (31 - (index & 31)))) != 0;
|
||||
}
|
||||
|
||||
public IEnumerator<bool> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < this.length; i++)
|
||||
yield return this.Get(i);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
count += this.array[i];
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public int LengthInBytes
|
||||
{
|
||||
get { return (this.length + 7) / 8; } //8 bits in a byte.
|
||||
}
|
||||
|
||||
public BitField Set(int index, bool value)
|
||||
{
|
||||
if (index < 0 || index >= this.length)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
if (value)
|
||||
{
|
||||
if ((this.array[index >> 5] & (1 << (31 - (index & 31)))) == 0)// If it's not already true
|
||||
this.trueCount++; // Increase true count
|
||||
this.array[index >> 5] |= (1 << (31 - index & 31));
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((this.array[index >> 5] & (1 << (31 - (index & 31)))) != 0)// If it's not already false
|
||||
this.trueCount--; // Decrease true count
|
||||
this.array[index >> 5] &= ~(1 << (31 - (index & 31)));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
internal BitField SetTrue(params int[] indices)
|
||||
{
|
||||
foreach (int index in indices)
|
||||
this.Set(index, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal BitField SetFalse(params int[] indices)
|
||||
{
|
||||
foreach (int index in indices)
|
||||
this.Set(index, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal BitField SetAll(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] = ~0;
|
||||
this.Validate();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
this.array[i] = 0;
|
||||
this.trueCount = 0;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
internal byte[] ToByteArray()
|
||||
{
|
||||
byte[] data = new byte[this.LengthInBytes];
|
||||
this.ToByteArray(data, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
internal void ToByteArray(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
this.ZeroUnusedBits();
|
||||
int end = this.Length / 32;
|
||||
for (int i = 0; i < end; i++)
|
||||
{
|
||||
buffer[offset++] = (byte)(this.array[i] >> 24);
|
||||
buffer[offset++] = (byte)(this.array[i] >> 16);
|
||||
buffer[offset++] = (byte)(this.array[i] >> 8);
|
||||
buffer[offset++] = (byte)(this.array[i] >> 0);
|
||||
}
|
||||
|
||||
int shift = 24;
|
||||
for (int i = end * 32; i < this.Length; i += 8)
|
||||
{
|
||||
buffer[offset++] = (byte)(this.array[this.array.Length - 1] >> shift);
|
||||
shift -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(this.array.Length * 16);
|
||||
for (int i = 0; i < this.Length; i++)
|
||||
{
|
||||
sb.Append(this.Get(i) ? 'T' : 'F');
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
return sb.ToString(0, sb.Length - 1);
|
||||
}
|
||||
|
||||
public int TrueCount
|
||||
{
|
||||
get { return this.trueCount; }
|
||||
}
|
||||
|
||||
void Validate()
|
||||
{
|
||||
this.ZeroUnusedBits();
|
||||
|
||||
// Update the population count
|
||||
uint count = 0;
|
||||
for (int i = 0; i < this.array.Length; i++)
|
||||
{
|
||||
uint v = (uint)this.array[i];
|
||||
v = v - ((v >> 1) & 0x55555555);
|
||||
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
|
||||
count += (((v + (v >> 4) & 0xF0F0F0F) * 0x1010101)) >> 24;
|
||||
}
|
||||
this.trueCount = (int)count;
|
||||
}
|
||||
|
||||
void ZeroUnusedBits()
|
||||
{
|
||||
if (this.array.Length == 0)
|
||||
return;
|
||||
|
||||
// Zero the unused bits
|
||||
int shift = 32 - this.length % 32;
|
||||
if (shift != 0)
|
||||
this.array[this.array.Length - 1] &= (-1 << shift);
|
||||
}
|
||||
|
||||
void Check(BitField value)
|
||||
{
|
||||
MonoTorrent.Check.Value(value);
|
||||
if (this.length != value.length)
|
||||
throw new ArgumentException("BitFields are of different lengths", "value");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public static class Check
|
||||
{
|
||||
static void DoCheck(object toCheck, string name)
|
||||
{
|
||||
if (toCheck == null)
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
|
||||
static void IsNullOrEmpty(string toCheck, string name)
|
||||
{
|
||||
DoCheck(toCheck, name);
|
||||
if (toCheck.Length == 0)
|
||||
throw new ArgumentException("Cannot be empty", name);
|
||||
}
|
||||
|
||||
public static void Address(object address)
|
||||
{
|
||||
DoCheck(address, "address");
|
||||
}
|
||||
|
||||
public static void AddressRange(object addressRange)
|
||||
{
|
||||
DoCheck(addressRange, "addressRange");
|
||||
}
|
||||
|
||||
public static void AddressRanges(object addressRanges)
|
||||
{
|
||||
DoCheck(addressRanges, "addressRanges");
|
||||
}
|
||||
|
||||
public static void Announces(object announces)
|
||||
{
|
||||
DoCheck(announces, "announces");
|
||||
}
|
||||
|
||||
public static void BaseDirectory(object baseDirectory)
|
||||
{
|
||||
DoCheck(baseDirectory, "baseDirectory");
|
||||
}
|
||||
|
||||
internal static void BaseType(Type baseType)
|
||||
{
|
||||
DoCheck(baseType, "baseType");
|
||||
}
|
||||
|
||||
internal static void Buffer(object buffer)
|
||||
{
|
||||
DoCheck(buffer, "buffer");
|
||||
}
|
||||
|
||||
internal static void Cache(object cache)
|
||||
{
|
||||
DoCheck(cache, "cache");
|
||||
}
|
||||
|
||||
public static void Data(object data)
|
||||
{
|
||||
DoCheck(data, "data");
|
||||
}
|
||||
|
||||
public static void Destination(object destination)
|
||||
{
|
||||
DoCheck(destination, "destination");
|
||||
}
|
||||
|
||||
public static void Endpoint(object endpoint)
|
||||
{
|
||||
DoCheck(endpoint, "endpoint");
|
||||
}
|
||||
|
||||
public static void File(object file)
|
||||
{
|
||||
DoCheck(file, "file");
|
||||
}
|
||||
|
||||
public static void Files(object files)
|
||||
{
|
||||
DoCheck(files, "files");
|
||||
}
|
||||
|
||||
public static void FileSource(object fileSource)
|
||||
{
|
||||
DoCheck(fileSource, "fileSource");
|
||||
}
|
||||
|
||||
public static void InfoHash(object infoHash)
|
||||
{
|
||||
DoCheck(infoHash, "infoHash");
|
||||
}
|
||||
|
||||
public static void Key(object key)
|
||||
{
|
||||
DoCheck(key, "key");
|
||||
}
|
||||
|
||||
public static void Limiter(object limiter)
|
||||
{
|
||||
DoCheck(limiter, "limiter");
|
||||
}
|
||||
|
||||
public static void Listener(object listener)
|
||||
{
|
||||
DoCheck(listener, "listener");
|
||||
}
|
||||
|
||||
public static void Location(object location)
|
||||
{
|
||||
DoCheck(location, "location");
|
||||
}
|
||||
|
||||
public static void MagnetLink(object magnetLink)
|
||||
{
|
||||
DoCheck(magnetLink, "magnetLink");
|
||||
}
|
||||
|
||||
public static void Manager(object manager)
|
||||
{
|
||||
DoCheck(manager, "manager");
|
||||
}
|
||||
|
||||
public static void Mappings(object mappings)
|
||||
{
|
||||
DoCheck(mappings, "mappings");
|
||||
}
|
||||
|
||||
public static void Metadata(object metadata)
|
||||
{
|
||||
DoCheck(metadata, "metadata");
|
||||
}
|
||||
|
||||
public static void Name(object name)
|
||||
{
|
||||
DoCheck(name, "name");
|
||||
}
|
||||
|
||||
public static void Path(object path)
|
||||
{
|
||||
DoCheck(path, "path");
|
||||
}
|
||||
|
||||
public static void Paths(object paths)
|
||||
{
|
||||
DoCheck(paths, "paths");
|
||||
}
|
||||
|
||||
public static void PathNotEmpty(string path)
|
||||
{
|
||||
IsNullOrEmpty(path, "path");
|
||||
}
|
||||
|
||||
public static void Peer(object peer)
|
||||
{
|
||||
DoCheck(peer, "peer");
|
||||
}
|
||||
|
||||
public static void Peers(object peers)
|
||||
{
|
||||
DoCheck(peers, "peers");
|
||||
}
|
||||
|
||||
public static void Picker(object picker)
|
||||
{
|
||||
DoCheck(picker, "picker");
|
||||
}
|
||||
|
||||
public static void Result(object result)
|
||||
{
|
||||
DoCheck(result, "result");
|
||||
}
|
||||
|
||||
public static void SavePath(object savePath)
|
||||
{
|
||||
DoCheck(savePath, "savePath");
|
||||
}
|
||||
|
||||
public static void Settings(object settings)
|
||||
{
|
||||
DoCheck(settings, "settings");
|
||||
}
|
||||
|
||||
internal static void SpecificType(Type specificType)
|
||||
{
|
||||
DoCheck(specificType, "specificType");
|
||||
}
|
||||
|
||||
public static void Stream(object stream)
|
||||
{
|
||||
DoCheck(stream, "stream");
|
||||
}
|
||||
|
||||
public static void Torrent(object torrent)
|
||||
{
|
||||
DoCheck(torrent, "torrent");
|
||||
}
|
||||
|
||||
public static void TorrentInformation(object torrentInformation)
|
||||
{
|
||||
DoCheck(torrentInformation, "torrentInformation");
|
||||
}
|
||||
|
||||
public static void TorrentSave(object torrentSave)
|
||||
{
|
||||
DoCheck(torrentSave, "torrentSave");
|
||||
}
|
||||
|
||||
public static void Tracker(object tracker)
|
||||
{
|
||||
DoCheck(tracker, "tracker");
|
||||
}
|
||||
|
||||
public static void Url(object url)
|
||||
{
|
||||
DoCheck(url, "url");
|
||||
}
|
||||
|
||||
public static void Uri(Uri uri)
|
||||
{
|
||||
DoCheck(uri, "uri");
|
||||
}
|
||||
|
||||
public static void Value(object value)
|
||||
{
|
||||
DoCheck(value, "value");
|
||||
}
|
||||
|
||||
public static void Writer(object writer)
|
||||
{
|
||||
DoCheck(writer, "writer");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
namespace MonoTorrent
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
DoNotDownload = 0,
|
||||
Lowest = 1,
|
||||
Low = 2,
|
||||
Normal = 4,
|
||||
High = 8,
|
||||
Highest = 16,
|
||||
Immediate = 32
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MonoTorrent.Exceptions
|
||||
{
|
||||
public class MessageException : TorrentException
|
||||
{
|
||||
public MessageException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public MessageException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public MessageException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public MessageException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public static class HashAlgoFactory
|
||||
{
|
||||
static Dictionary<Type, Type> algos = new Dictionary<Type, Type>();
|
||||
|
||||
static HashAlgoFactory()
|
||||
{
|
||||
Register<MD5, MD5CryptoServiceProvider>();
|
||||
Register<SHA1, SHA1CryptoServiceProvider>();
|
||||
}
|
||||
|
||||
public static void Register<T, U>()
|
||||
where T : HashAlgorithm
|
||||
where U : HashAlgorithm
|
||||
{
|
||||
Register(typeof(T), typeof(U));
|
||||
}
|
||||
|
||||
public static void Register(Type baseType, Type specificType)
|
||||
{
|
||||
Check.BaseType(baseType);
|
||||
Check.SpecificType(specificType);
|
||||
|
||||
lock (algos)
|
||||
algos[baseType] = specificType;
|
||||
}
|
||||
|
||||
public static T Create<T>()
|
||||
where T : HashAlgorithm
|
||||
{
|
||||
if (algos.ContainsKey(typeof(T)))
|
||||
return (T)Activator.CreateInstance(algos[typeof(T)]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public class Hashes
|
||||
{
|
||||
#region Constants
|
||||
/// <summary>
|
||||
/// Hash code length (in bytes)
|
||||
/// </summary>
|
||||
internal static readonly int HashCodeLength = 20;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private int count;
|
||||
private byte[] hashData;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Number of Hashes (equivalent to number of Pieces)
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal Hashes(byte[] hashData, int count)
|
||||
{
|
||||
this.hashData = hashData;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether a calculated hash is equal to our stored hash
|
||||
/// </summary>
|
||||
/// <param name="hash">Hash code to check</param>
|
||||
/// <param name="hashIndex">Index of hash/piece to verify against</param>
|
||||
/// <returns>true iff hash is equal to our stored hash, false otherwise</returns>
|
||||
public bool IsValid(byte[] hash, int hashIndex)
|
||||
{
|
||||
if (hash == null)
|
||||
throw new ArgumentNullException("hash");
|
||||
|
||||
if (hash.Length != HashCodeLength)
|
||||
throw new ArgumentException(string.Format("Hash must be {0} bytes in length", HashCodeLength), "hash");
|
||||
|
||||
if (hashIndex < 0 || hashIndex > this.count)
|
||||
throw new ArgumentOutOfRangeException("hashIndex", string.Format("hashIndex must be between 0 and {0}", this.count));
|
||||
|
||||
int start = hashIndex * HashCodeLength;
|
||||
for (int i = 0; i < HashCodeLength; i++)
|
||||
if (hash[i] != this.hashData[i + start])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash for a specific piece
|
||||
/// </summary>
|
||||
/// <param name="hashIndex">Piece/hash index to return</param>
|
||||
/// <returns>byte[] (length HashCodeLength) containing hashdata</returns>
|
||||
public byte[] ReadHash(int hashIndex)
|
||||
{
|
||||
if (hashIndex < 0 || hashIndex >= this.count)
|
||||
throw new ArgumentOutOfRangeException("hashIndex");
|
||||
|
||||
// Read out our specified piece's hash data
|
||||
byte[] hash = new byte[HashCodeLength];
|
||||
Buffer.BlockCopy(this.hashData, hashIndex * HashCodeLength, hash, 0, HashCodeLength);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public class InfoHash : IEquatable<InfoHash>
|
||||
{
|
||||
static Dictionary<char, byte> base32DecodeTable;
|
||||
|
||||
static InfoHash()
|
||||
{
|
||||
base32DecodeTable = new Dictionary<char, byte>();
|
||||
string table = "abcdefghijklmnopqrstuvwxyz234567";
|
||||
for (int i = 0; i < table.Length; i++)
|
||||
base32DecodeTable[table[i]] = (byte)i;
|
||||
}
|
||||
|
||||
byte[] hash;
|
||||
|
||||
internal byte[] Hash
|
||||
{
|
||||
get { return hash; }
|
||||
}
|
||||
|
||||
public InfoHash(byte[] infoHash)
|
||||
{
|
||||
Check.InfoHash(infoHash);
|
||||
if (infoHash.Length != 20)
|
||||
throw new ArgumentException("Infohash must be exactly 20 bytes long");
|
||||
hash = (byte[])infoHash.Clone();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as InfoHash);
|
||||
}
|
||||
|
||||
public bool Equals(byte[] other)
|
||||
{
|
||||
return other == null || other.Length != 20 ? false : Toolbox.ByteMatch(Hash, other);
|
||||
}
|
||||
|
||||
public bool Equals(InfoHash other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Equality is based generally on checking 20 positions, checking 4 should be enough
|
||||
// for the hashcode as infohashes are randomly distributed.
|
||||
return Hash[0] | (Hash[1] << 8) | (Hash[2] << 16) | (Hash[3] << 24);
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
return (byte[])hash.Clone();
|
||||
}
|
||||
|
||||
public string ToHex()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
for (int i = 0; i < hash.Length; i++)
|
||||
{
|
||||
string hex = hash[i].ToString("X");
|
||||
if (hex.Length != 2)
|
||||
sb.Append("0");
|
||||
sb.Append(hex);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BitConverter.ToString(hash);
|
||||
}
|
||||
|
||||
public string UrlEncode()
|
||||
{
|
||||
return UriHelper.UrlEncode(Hash);
|
||||
}
|
||||
|
||||
public static bool operator ==(InfoHash left, InfoHash right)
|
||||
{
|
||||
if ((object)left == null)
|
||||
return (object)right == null;
|
||||
if ((object)right == null)
|
||||
return false;
|
||||
return Toolbox.ByteMatch(left.Hash, right.Hash);
|
||||
}
|
||||
|
||||
public static bool operator !=(InfoHash left, InfoHash right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public static InfoHash FromBase32(string infoHash)
|
||||
{
|
||||
Check.InfoHash(infoHash);
|
||||
if (infoHash.Length != 32)
|
||||
throw new ArgumentException("Infohash must be a base32 encoded 32 character string");
|
||||
|
||||
infoHash = infoHash.ToLower();
|
||||
int infohashOffset = 0;
|
||||
byte[] hash = new byte[20];
|
||||
var temp = new byte[8];
|
||||
for (int i = 0; i < hash.Length;)
|
||||
{
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (!base32DecodeTable.TryGetValue(infoHash[infohashOffset++], out temp[j]))
|
||||
throw new ArgumentException("infoHash", "Value is not a valid base32 encoded string");
|
||||
|
||||
//8 * 5bits = 40 bits = 5 bytes
|
||||
hash[i++] = (byte)((temp[0] << 3) | (temp[1] >> 2));
|
||||
hash[i++] = (byte)((temp[1] << 6) | (temp[2] << 1) | (temp[3] >> 4));
|
||||
hash[i++] = (byte)((temp[3] << 4) | (temp[4] >> 1));
|
||||
hash[i++] = (byte)((temp[4] << 7) | (temp[5] << 2) | (temp[6] >> 3));
|
||||
hash[i++] = (byte)((temp[6] << 5) | temp[7]);
|
||||
}
|
||||
|
||||
return new InfoHash(hash);
|
||||
}
|
||||
|
||||
public static InfoHash FromHex(string infoHash)
|
||||
{
|
||||
Check.InfoHash(infoHash);
|
||||
if (infoHash.Length != 40)
|
||||
throw new ArgumentException("Infohash must be 40 characters long");
|
||||
|
||||
byte[] hash = new byte[20];
|
||||
for (int i = 0; i < hash.Length; i++)
|
||||
hash[i] = byte.Parse(infoHash.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
|
||||
return new InfoHash(hash);
|
||||
}
|
||||
|
||||
public static InfoHash FromMagnetLink(string magnetLink)
|
||||
{
|
||||
Check.MagnetLink(magnetLink);
|
||||
if (!magnetLink.StartsWith("magnet:?"))
|
||||
throw new ArgumentException("Invalid magnet link format");
|
||||
magnetLink = magnetLink.Substring("magnet:?".Length);
|
||||
int hashStart = magnetLink.IndexOf("xt=urn:btih:");
|
||||
if (hashStart == -1)
|
||||
throw new ArgumentException("Magnet link does not contain an infohash");
|
||||
hashStart += "xt=urn:btih:".Length;
|
||||
|
||||
int hashEnd = magnetLink.IndexOf('&', hashStart);
|
||||
if (hashEnd == -1)
|
||||
hashEnd = magnetLink.Length;
|
||||
|
||||
switch (hashEnd - hashStart)
|
||||
{
|
||||
case 32:
|
||||
return FromBase32(magnetLink.Substring(hashStart, 32));
|
||||
case 40:
|
||||
return FromHex(magnetLink.Substring(hashStart, 40));
|
||||
default:
|
||||
throw new ArgumentException("Infohash must be base32 or hex encoded.");
|
||||
}
|
||||
}
|
||||
|
||||
public static InfoHash UrlDecode(string infoHash)
|
||||
{
|
||||
Check.InfoHash(infoHash);
|
||||
return new InfoHash(UriHelper.UrlDecode(infoHash));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public class MagnetLink
|
||||
{
|
||||
public RawTrackerTier AnnounceUrls
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public InfoHash InfoHash
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public List<string> Webseeds
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public MagnetLink(string url)
|
||||
{
|
||||
Check.Url(url);
|
||||
AnnounceUrls = new RawTrackerTier();
|
||||
Webseeds = new List<string>();
|
||||
|
||||
ParseMagnetLink(url);
|
||||
}
|
||||
|
||||
void ParseMagnetLink(string url)
|
||||
{
|
||||
string[] splitStr = url.Split('?');
|
||||
if (splitStr.Length == 0 || splitStr[0] != "magnet:")
|
||||
throw new FormatException("The magnet link must start with 'magnet:?'.");
|
||||
|
||||
if (splitStr.Length == 1)
|
||||
return;//no parametter
|
||||
|
||||
string[] parameters = splitStr[1].Split('&', ';');
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
string[] keyval = parameters[i].Split('=');
|
||||
if (keyval.Length != 2)
|
||||
throw new FormatException("A field-value pair of the magnet link contain more than one equal'.");
|
||||
switch (keyval[0].Substring(0, 2))
|
||||
{
|
||||
case "xt"://exact topic
|
||||
if (InfoHash != null)
|
||||
throw new FormatException("More than one infohash in magnet link is not allowed.");
|
||||
|
||||
string val = keyval[1].Substring(9);
|
||||
switch (keyval[1].Substring(0, 9))
|
||||
{
|
||||
case "urn:sha1:"://base32 hash
|
||||
case "urn:btih:":
|
||||
if (val.Length == 32)
|
||||
InfoHash = InfoHash.FromBase32(val);
|
||||
else if (val.Length == 40)
|
||||
InfoHash = InfoHash.FromHex(val);
|
||||
else
|
||||
throw new FormatException("Infohash must be base32 or hex encoded.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "tr"://address tracker
|
||||
var bytes = UriHelper.UrlDecode(keyval[1]);
|
||||
AnnounceUrls.Add(Encoding.UTF8.GetString(bytes));
|
||||
break;
|
||||
case "as"://Acceptable Source
|
||||
Webseeds.Add(keyval[1]);
|
||||
break;
|
||||
case "dn"://display name
|
||||
var name = UriHelper.UrlDecode(keyval[1]);
|
||||
Name = Encoding.UTF8.GetString(name);
|
||||
break;
|
||||
case "xl"://exact length
|
||||
case "xs":// eXact Source - P2P link.
|
||||
case "kt"://keyword topic
|
||||
case "mt"://manifest topic
|
||||
//not supported for moment
|
||||
break;
|
||||
default:
|
||||
//not supported
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
namespace MonoTorrent.Messages
|
||||
{
|
||||
interface IMessage
|
||||
{
|
||||
int ByteLength { get; }
|
||||
|
||||
byte[] Encode();
|
||||
int Encode(byte[] buffer, int offset);
|
||||
|
||||
void Decode(byte[] buffer, int offset, int length);
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using MonoTorrent.Exceptions;
|
||||
|
||||
namespace MonoTorrent.Messages
|
||||
{
|
||||
public abstract class Message : IMessage
|
||||
{
|
||||
public abstract int ByteLength { get; }
|
||||
|
||||
protected int CheckWritten(int written)
|
||||
{
|
||||
if (written != this.ByteLength)
|
||||
throw new MessageException("Message encoded incorrectly. Incorrect number of bytes written");
|
||||
return written;
|
||||
}
|
||||
|
||||
public abstract void Decode(byte[] buffer, int offset, int length);
|
||||
|
||||
public byte[] Encode()
|
||||
{
|
||||
byte[] buffer = new byte[this.ByteLength];
|
||||
this.Encode(buffer, 0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public abstract int Encode(byte[] buffer, int offset);
|
||||
|
||||
static public byte ReadByte(byte[] buffer, int offset)
|
||||
{
|
||||
return buffer[offset];
|
||||
}
|
||||
|
||||
static public byte ReadByte(byte[] buffer, ref int offset)
|
||||
{
|
||||
byte b = buffer[offset];
|
||||
offset++;
|
||||
return b;
|
||||
}
|
||||
|
||||
static public byte[] ReadBytes(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadBytes(buffer, ref offset, count);
|
||||
}
|
||||
|
||||
static public byte[] ReadBytes(byte[] buffer, ref int offset, int count)
|
||||
{
|
||||
byte[] result = new byte[count];
|
||||
Buffer.BlockCopy(buffer, offset, result, 0, count);
|
||||
offset += count;
|
||||
return result;
|
||||
}
|
||||
|
||||
static public short ReadShort(byte[] buffer, int offset)
|
||||
{
|
||||
return ReadShort(buffer, ref offset);
|
||||
}
|
||||
|
||||
static public short ReadShort(byte[] buffer, ref int offset)
|
||||
{
|
||||
short ret = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(buffer, offset));
|
||||
offset += 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public string ReadString(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadString(buffer, ref offset, count);
|
||||
}
|
||||
|
||||
static public string ReadString(byte[] buffer, ref int offset, int count)
|
||||
{
|
||||
string s = System.Text.Encoding.ASCII.GetString(buffer, offset, count);
|
||||
offset += count;
|
||||
return s;
|
||||
}
|
||||
|
||||
static public int ReadInt(byte[] buffer, int offset)
|
||||
{
|
||||
return ReadInt(buffer, ref offset);
|
||||
}
|
||||
|
||||
static public int ReadInt(byte[] buffer, ref int offset)
|
||||
{
|
||||
int ret = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buffer, offset));
|
||||
offset += 4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public long ReadLong(byte[] buffer, int offset)
|
||||
{
|
||||
return ReadLong(buffer, ref offset);
|
||||
}
|
||||
|
||||
static public long ReadLong(byte[] buffer, ref int offset)
|
||||
{
|
||||
long ret = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(buffer, offset));
|
||||
offset += 8;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, byte value)
|
||||
{
|
||||
buffer[offset] = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static public int Write(byte[] dest, int destOffset, byte[] src, int srcOffset, int count)
|
||||
{
|
||||
Buffer.BlockCopy(src, srcOffset, dest, destOffset, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, ushort value)
|
||||
{
|
||||
return Write(buffer, offset, (short)value);
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, short value)
|
||||
{
|
||||
offset += Write(buffer, offset, (byte)(value >> 8));
|
||||
offset += Write(buffer, offset, (byte)value);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, int value)
|
||||
{
|
||||
offset += Write(buffer, offset, (byte)(value >> 24));
|
||||
offset += Write(buffer, offset, (byte)(value >> 16));
|
||||
offset += Write(buffer, offset, (byte)(value >> 8));
|
||||
offset += Write(buffer, offset, (byte)(value));
|
||||
return 4;
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, uint value)
|
||||
{
|
||||
return Write(buffer, offset, (int)value);
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, long value)
|
||||
{
|
||||
offset += Write(buffer, offset, (int)(value >> 32));
|
||||
offset += Write(buffer, offset, (int)value);
|
||||
return 8;
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, ulong value)
|
||||
{
|
||||
return Write(buffer, offset, (long)value);
|
||||
}
|
||||
|
||||
static public int Write(byte[] buffer, int offset, byte[] value)
|
||||
{
|
||||
return Write(buffer, offset, value, 0, value.Length);
|
||||
}
|
||||
|
||||
static public int WriteAscii(byte[] buffer, int offset, string text)
|
||||
{
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
Write(buffer, offset + i, (byte)text[i]);
|
||||
return text.Length;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
|
||||
<Version>9.0.21022</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,100 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using MonoTorrent.BEncoding;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public class RawTrackerTier : IList<string>
|
||||
{
|
||||
public string this[int index]
|
||||
{
|
||||
get { return ((BEncodedString)Tier[index]).Text; }
|
||||
set { Tier[index] = new BEncodedString(value); }
|
||||
}
|
||||
|
||||
internal BEncodedList Tier
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RawTrackerTier()
|
||||
: this(new BEncodedList())
|
||||
{
|
||||
}
|
||||
|
||||
public RawTrackerTier(BEncodedList tier)
|
||||
{
|
||||
Tier = tier;
|
||||
}
|
||||
|
||||
public RawTrackerTier(IEnumerable<string> announces)
|
||||
: this()
|
||||
{
|
||||
foreach (var v in announces)
|
||||
Add(v);
|
||||
}
|
||||
|
||||
public int IndexOf(string item)
|
||||
{
|
||||
return Tier.IndexOf((BEncodedString)item);
|
||||
}
|
||||
|
||||
public void Insert(int index, string item)
|
||||
{
|
||||
Tier.Insert(index, (BEncodedString)item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Tier.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Add(string item)
|
||||
{
|
||||
Tier.Add((BEncodedString)item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Tier.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(string item)
|
||||
{
|
||||
return Tier.Contains((BEncodedString)item);
|
||||
}
|
||||
|
||||
public void CopyTo(string[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var s in this)
|
||||
array[arrayIndex++] = s;
|
||||
}
|
||||
|
||||
public bool Remove(string item)
|
||||
{
|
||||
return Tier.Remove((BEncodedString)item);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return Tier.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return Tier.IsReadOnly; }
|
||||
}
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
foreach (BEncodedString v in Tier)
|
||||
yield return v.Text;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using MonoTorrent.BEncoding;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public class RawTrackerTiers : IList<RawTrackerTier>
|
||||
{
|
||||
BEncodedList Tiers
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RawTrackerTiers()
|
||||
: this(new BEncodedList())
|
||||
{
|
||||
}
|
||||
|
||||
public RawTrackerTiers(BEncodedList tiers)
|
||||
{
|
||||
Tiers = tiers;
|
||||
}
|
||||
|
||||
public int IndexOf(RawTrackerTier item)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
for (int i = 0; i < Tiers.Count; i++)
|
||||
if (item.Tier == Tiers[i])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, RawTrackerTier item)
|
||||
{
|
||||
Tiers.Insert(index, item.Tier);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Tiers.RemoveAt(index);
|
||||
}
|
||||
|
||||
public RawTrackerTier this[int index]
|
||||
{
|
||||
get { return new RawTrackerTier((BEncodedList)Tiers[index]); }
|
||||
set { Tiers[index] = value.Tier; }
|
||||
}
|
||||
|
||||
public void Add(RawTrackerTier item)
|
||||
{
|
||||
Tiers.Add(item.Tier);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<RawTrackerTier> tiers)
|
||||
{
|
||||
foreach (var v in tiers)
|
||||
Add(v);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Tiers.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(RawTrackerTier item)
|
||||
{
|
||||
return IndexOf(item) != -1;
|
||||
}
|
||||
|
||||
public void CopyTo(RawTrackerTier[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var v in this)
|
||||
array[arrayIndex++] = v;
|
||||
}
|
||||
|
||||
public bool Remove(RawTrackerTier item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
if (index != -1)
|
||||
RemoveAt(index);
|
||||
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return Tiers.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return Tiers.IsReadOnly; }
|
||||
}
|
||||
|
||||
public IEnumerator<RawTrackerTier> GetEnumerator()
|
||||
{
|
||||
foreach (var v in Tiers)
|
||||
yield return new RawTrackerTier((BEncodedList)v);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
public delegate long Operation<T>(T target);
|
||||
|
||||
public static class Toolbox
|
||||
{
|
||||
private static Random r = new Random();
|
||||
public static int Count<T>(IEnumerable<T> enumerable, Predicate<T> predicate)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (T t in enumerable)
|
||||
if (predicate(t))
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static long Accumulate<T>(IEnumerable<T> enumerable, Operation<T> action)
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
foreach (T t in enumerable)
|
||||
count += action(t);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void RaiseAsyncEvent<T>(EventHandler<T> e, object o, T args)
|
||||
where T : EventArgs
|
||||
{
|
||||
if (e == null)
|
||||
return;
|
||||
|
||||
ThreadPool.QueueUserWorkItem(delegate
|
||||
{
|
||||
if (e != null)
|
||||
e(o, args);
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// Randomizes the contents of the array
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="array"></param>
|
||||
public static void Randomize<T>(List<T> array)
|
||||
{
|
||||
List<T> clone = new List<T>(array);
|
||||
array.Clear();
|
||||
|
||||
while (clone.Count > 0)
|
||||
{
|
||||
int index = r.Next(0, clone.Count);
|
||||
array.Add(clone[index]);
|
||||
clone.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches the positions of two elements in an array
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="second"></param>
|
||||
public static void Switch<T>(IList<T> array, int first, int second)
|
||||
{
|
||||
T obj = array[first];
|
||||
array[first] = array[second];
|
||||
array[second] = obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the contents of two byte arrays are equal
|
||||
/// </summary>
|
||||
/// <param name="array1">The first array</param>
|
||||
/// <param name="array2">The second array</param>
|
||||
/// <returns>True if the arrays are equal, false if they aren't</returns>
|
||||
public static bool ByteMatch(byte[] array1, byte[] array2)
|
||||
{
|
||||
if (array1 == null)
|
||||
throw new ArgumentNullException("array1");
|
||||
if (array2 == null)
|
||||
throw new ArgumentNullException("array2");
|
||||
|
||||
if (array1.Length != array2.Length)
|
||||
return false;
|
||||
|
||||
return ByteMatch(array1, 0, array2, 0, array1.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the contents of two byte arrays are equal
|
||||
/// </summary>
|
||||
/// <param name="array1">The first array</param>
|
||||
/// <param name="array2">The second array</param>
|
||||
/// <param name="offset1">The starting index for the first array</param>
|
||||
/// <param name="offset2">The starting index for the second array</param>
|
||||
/// <param name="count">The number of bytes to check</param>
|
||||
/// <returns></returns>
|
||||
public static bool ByteMatch(byte[] array1, int offset1, byte[] array2, int offset2, int count)
|
||||
{
|
||||
if (array1 == null)
|
||||
throw new ArgumentNullException("array1");
|
||||
if (array2 == null)
|
||||
throw new ArgumentNullException("array2");
|
||||
|
||||
// If either of the arrays is too small, they're not equal
|
||||
if ((array1.Length - offset1) < count || (array2.Length - offset2) < count)
|
||||
return false;
|
||||
|
||||
// Check if any elements are unequal
|
||||
for (int i = 0; i < count; i++)
|
||||
if (array1[offset1 + i] != array2[offset2 + i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,885 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using MonoTorrent.BEncoding;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
/// <summary>
|
||||
/// The "Torrent" class for both Tracker and Client should inherit from this
|
||||
/// as it contains the fields that are common to both.
|
||||
/// </summary>
|
||||
public class Torrent : IEquatable<Torrent>
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private BEncodedDictionary originalDictionary;
|
||||
private BEncodedValue azureusProperties;
|
||||
private IList<RawTrackerTier> announceUrls;
|
||||
private string comment;
|
||||
private string createdBy;
|
||||
private DateTime creationDate;
|
||||
private byte[] ed2k;
|
||||
private string encoding;
|
||||
internal InfoHash infoHash;
|
||||
private bool isPrivate;
|
||||
protected string name;
|
||||
private BEncodedList nodes;
|
||||
protected int pieceLength;
|
||||
protected Hashes pieces;
|
||||
private string publisher;
|
||||
private string publisherUrl;
|
||||
private byte[] sha1;
|
||||
protected long size;
|
||||
private string source;
|
||||
protected TorrentFile[] torrentFiles;
|
||||
protected string torrentPath;
|
||||
private List<string> getRightHttpSeeds;
|
||||
private byte[] metadata;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
|
||||
#region Properties
|
||||
|
||||
internal byte[] Metadata
|
||||
{
|
||||
get { return this.metadata; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The announce URLs contained within the .torrent file
|
||||
/// </summary>
|
||||
public IList<RawTrackerTier> AnnounceUrls
|
||||
{
|
||||
get { return this.announceUrls; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary is specific for azureus client
|
||||
/// It can contain
|
||||
/// dht_backup_enable (number)
|
||||
/// Content (dictionnary)
|
||||
/// Publisher
|
||||
/// Description
|
||||
/// Title
|
||||
/// Creation Date
|
||||
/// Content Hash
|
||||
/// Revision Date
|
||||
/// Thumbnail (string) = Base64 encoded image
|
||||
/// Progressive
|
||||
/// Speed Bps (number)
|
||||
/// but not useful for MT
|
||||
/// </summary>
|
||||
public BEncodedValue AzureusProperties
|
||||
{
|
||||
get { return this.azureusProperties; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The comment contained within the .torrent file
|
||||
/// </summary>
|
||||
public string Comment
|
||||
{
|
||||
get { return this.comment; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The optional string showing who/what created the .torrent
|
||||
/// </summary>
|
||||
public string CreatedBy
|
||||
{
|
||||
get { return this.createdBy; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The creation date of the .torrent file
|
||||
/// </summary>
|
||||
public DateTime CreationDate
|
||||
{
|
||||
get { return this.creationDate; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The optional ED2K hash contained within the .torrent file
|
||||
/// </summary>
|
||||
public byte[] ED2K
|
||||
{
|
||||
get { return this.ed2k; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The encoding used by the client that created the .torrent file
|
||||
/// </summary>
|
||||
public string Encoding
|
||||
{
|
||||
get { return this.encoding; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The list of files contained within the .torrent which are available for download
|
||||
/// </summary>
|
||||
public TorrentFile[] Files
|
||||
{
|
||||
get { return this.torrentFiles; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the infohash that is generated by putting the "Info" section of a .torrent
|
||||
/// through a ManagedSHA1 hasher.
|
||||
/// </summary>
|
||||
public InfoHash InfoHash
|
||||
{
|
||||
get { return this.infoHash; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Shows whether DHT is allowed or not. If it is a private torrent, no peer
|
||||
/// sharing should be allowed.
|
||||
/// </summary>
|
||||
public bool IsPrivate
|
||||
{
|
||||
get { return this.isPrivate; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// In the case of a single file torrent, this is the name of the file.
|
||||
/// In the case of a multi file torrent, it is the name of the root folder.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
private set { this.name = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// FIXME: No idea what this is.
|
||||
/// </summary>
|
||||
public BEncodedList Nodes
|
||||
{
|
||||
get { return this.nodes; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The length of each piece in bytes.
|
||||
/// </summary>
|
||||
public int PieceLength
|
||||
{
|
||||
get { return this.pieceLength; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the array of hashes contained within the torrent.
|
||||
/// </summary>
|
||||
public Hashes Pieces
|
||||
{
|
||||
get { return this.pieces; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The name of the Publisher
|
||||
/// </summary>
|
||||
public string Publisher
|
||||
{
|
||||
get { return this.publisher; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The Url of the publisher of either the content or the .torrent file
|
||||
/// </summary>
|
||||
public string PublisherUrl
|
||||
{
|
||||
get { return this.publisherUrl; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The optional SHA1 hash contained within the .torrent file
|
||||
/// </summary>
|
||||
public byte[] SHA1
|
||||
{
|
||||
get { return this.sha1; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The total size of all the files that have to be downloaded.
|
||||
/// </summary>
|
||||
public long Size
|
||||
{
|
||||
get { return this.size; }
|
||||
private set { this.size = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The source of the .torrent file
|
||||
/// </summary>
|
||||
public string Source
|
||||
{
|
||||
get { return this.source; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the path at which the .torrent file is located
|
||||
/// </summary>
|
||||
public string TorrentPath
|
||||
{
|
||||
get { return this.torrentPath; }
|
||||
internal set { this.torrentPath = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the http-based seeding (getright protocole)
|
||||
/// </summary>
|
||||
public List<string> GetRightHttpSeeds
|
||||
{
|
||||
get { return this.getRightHttpSeeds; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected Torrent()
|
||||
{
|
||||
this.announceUrls = new RawTrackerTiers();
|
||||
this.comment = string.Empty;
|
||||
this.createdBy = string.Empty;
|
||||
this.creationDate = new DateTime(1970, 1, 1, 0, 0, 0);
|
||||
this.encoding = string.Empty;
|
||||
this.name = string.Empty;
|
||||
this.publisher = string.Empty;
|
||||
this.publisherUrl = string.Empty;
|
||||
this.source = string.Empty;
|
||||
this.getRightHttpSeeds = new List<string>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as Torrent);
|
||||
}
|
||||
|
||||
public bool Equals(Torrent other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return this.infoHash == other.infoHash;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.infoHash.GetHashCode();
|
||||
}
|
||||
|
||||
internal byte[] ToBytes()
|
||||
{
|
||||
return this.originalDictionary.Encode();
|
||||
}
|
||||
|
||||
internal BEncodedDictionary ToDictionary()
|
||||
{
|
||||
// Give the user a copy of the original dictionary.
|
||||
return BEncodedValue.Clone(this.originalDictionary);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// This method is called internally to read out the hashes from the info section of the
|
||||
/// .torrent file.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte[]containing the hashes from the .torrent file</param>
|
||||
private void LoadHashPieces(byte[] data)
|
||||
{
|
||||
if (data.Length % 20 != 0)
|
||||
throw new TorrentException("Invalid infohash detected");
|
||||
|
||||
this.pieces = new Hashes(data, data.Length / 20);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method is called internally to load in all the files found within the "Files" section
|
||||
/// of the .torrents infohash
|
||||
/// </summary>
|
||||
/// <param name="list">The list containing the files available to download</param>
|
||||
private void LoadTorrentFiles(BEncodedList list)
|
||||
{
|
||||
List<TorrentFile> files = new List<TorrentFile>();
|
||||
int endIndex;
|
||||
long length;
|
||||
string path;
|
||||
byte[] md5sum;
|
||||
byte[] ed2k;
|
||||
byte[] sha1;
|
||||
int startIndex;
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
|
||||
foreach (BEncodedDictionary dict in list)
|
||||
{
|
||||
length = 0;
|
||||
path = null;
|
||||
md5sum = null;
|
||||
ed2k = null;
|
||||
sha1 = null;
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in dict)
|
||||
{
|
||||
switch (keypair.Key.Text)
|
||||
{
|
||||
case ("sha1"):
|
||||
sha1 = ((BEncodedString)keypair.Value).TextBytes;
|
||||
break;
|
||||
|
||||
case ("ed2k"):
|
||||
ed2k = ((BEncodedString)keypair.Value).TextBytes;
|
||||
break;
|
||||
|
||||
case ("length"):
|
||||
length = long.Parse(keypair.Value.ToString());
|
||||
break;
|
||||
|
||||
case ("path.utf-8"):
|
||||
foreach (BEncodedString str in ((BEncodedList)keypair.Value))
|
||||
{
|
||||
sb.Append(str.Text);
|
||||
sb.Append(Path.DirectorySeparatorChar);
|
||||
}
|
||||
path = sb.ToString(0, sb.Length - 1);
|
||||
sb.Remove(0, sb.Length);
|
||||
break;
|
||||
|
||||
case ("path"):
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
foreach (BEncodedString str in ((BEncodedList)keypair.Value))
|
||||
{
|
||||
sb.Append(str.Text);
|
||||
sb.Append(Path.DirectorySeparatorChar);
|
||||
}
|
||||
path = sb.ToString(0, sb.Length - 1);
|
||||
sb.Remove(0, sb.Length);
|
||||
}
|
||||
break;
|
||||
|
||||
case ("md5sum"):
|
||||
md5sum = ((BEncodedString)keypair.Value).TextBytes;
|
||||
break;
|
||||
|
||||
default:
|
||||
break; //FIXME: Log unknown values
|
||||
}
|
||||
}
|
||||
|
||||
// A zero length file always belongs to the same piece as the previous file
|
||||
if (length == 0)
|
||||
{
|
||||
if (files.Count > 0)
|
||||
{
|
||||
startIndex = files[files.Count - 1].EndPieceIndex;
|
||||
endIndex = files[files.Count - 1].EndPieceIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
startIndex = 0;
|
||||
endIndex = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startIndex = (int)(this.size / this.pieceLength);
|
||||
endIndex = (int)((this.size + length) / this.pieceLength);
|
||||
if ((this.size + length) % this.pieceLength == 0)
|
||||
endIndex--;
|
||||
}
|
||||
this.size += length;
|
||||
files.Add(new TorrentFile(path, length, path, startIndex, endIndex, md5sum, ed2k, sha1));
|
||||
}
|
||||
|
||||
this.torrentFiles = files.ToArray();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method is called internally to load the information found within the "Info" section
|
||||
/// of the .torrent file
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary representing the Info section of the .torrent file</param>
|
||||
private void ProcessInfo(BEncodedDictionary dictionary)
|
||||
{
|
||||
this.metadata = dictionary.Encode();
|
||||
this.pieceLength = int.Parse(dictionary["piece length"].ToString());
|
||||
this.LoadHashPieces(((BEncodedString)dictionary["pieces"]).TextBytes);
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in dictionary)
|
||||
{
|
||||
switch (keypair.Key.Text)
|
||||
{
|
||||
case ("source"):
|
||||
this.source = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("sha1"):
|
||||
this.sha1 = ((BEncodedString)keypair.Value).TextBytes;
|
||||
break;
|
||||
|
||||
case ("ed2k"):
|
||||
this.ed2k = ((BEncodedString)keypair.Value).TextBytes;
|
||||
break;
|
||||
|
||||
case ("publisher-url.utf-8"):
|
||||
if (keypair.Value.ToString().Length > 0)
|
||||
this.publisherUrl = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("publisher-url"):
|
||||
if ((String.IsNullOrEmpty(this.publisherUrl)) && (keypair.Value.ToString().Length > 0))
|
||||
this.publisherUrl = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("publisher.utf-8"):
|
||||
if (keypair.Value.ToString().Length > 0)
|
||||
this.publisher = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("publisher"):
|
||||
if ((String.IsNullOrEmpty(this.publisher)) && (keypair.Value.ToString().Length > 0))
|
||||
this.publisher = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("files"):
|
||||
this.LoadTorrentFiles(((BEncodedList)keypair.Value));
|
||||
break;
|
||||
|
||||
case ("name.utf-8"):
|
||||
if (keypair.Value.ToString().Length > 0)
|
||||
this.name = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("name"):
|
||||
if ((String.IsNullOrEmpty(this.name)) && (keypair.Value.ToString().Length > 0))
|
||||
this.name = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("piece length"): // Already handled
|
||||
break;
|
||||
|
||||
case ("length"):
|
||||
break; // This is a singlefile torrent
|
||||
|
||||
case ("private"):
|
||||
this.isPrivate = (keypair.Value.ToString() == "1") ? true : false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.torrentFiles == null) // Not a multi-file torrent
|
||||
{
|
||||
long length = long.Parse(dictionary["length"].ToString());
|
||||
this.size = length;
|
||||
string path = this.name;
|
||||
byte[] md5 = (dictionary.ContainsKey("md5")) ? ((BEncodedString)dictionary["md5"]).TextBytes : null;
|
||||
byte[] ed2k = (dictionary.ContainsKey("ed2k")) ? ((BEncodedString)dictionary["ed2k"]).TextBytes : null;
|
||||
byte[] sha1 = (dictionary.ContainsKey("sha1")) ? ((BEncodedString)dictionary["sha1"]).TextBytes : null;
|
||||
|
||||
this.torrentFiles = new TorrentFile[1];
|
||||
int endPiece = Math.Min(this.Pieces.Count - 1, (int)((this.size + (this.pieceLength - 1)) / this.pieceLength));
|
||||
this.torrentFiles[0] = new TorrentFile(path, length, path, 0, endPiece, md5, ed2k, sha1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
|
||||
#region Loading methods
|
||||
|
||||
/// <summary>
|
||||
/// This method loads a .torrent file from the specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to load the .torrent file from</param>
|
||||
public static Torrent Load(string path)
|
||||
{
|
||||
Check.Path(path);
|
||||
|
||||
using (Stream s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
return Torrent.Load(s, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a torrent from a byte[] containing the bencoded data
|
||||
/// </summary>
|
||||
/// <param name="data">The byte[] containing the data</param>
|
||||
/// <returns></returns>
|
||||
public static Torrent Load(byte[] data)
|
||||
{
|
||||
Check.Data(data);
|
||||
|
||||
using (MemoryStream s = new MemoryStream(data))
|
||||
return Load(s, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent from the supplied stream
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the data to load</param>
|
||||
/// <returns></returns>
|
||||
public static Torrent Load(Stream stream)
|
||||
{
|
||||
Check.Stream(stream);
|
||||
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
return Torrent.Load(stream, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent file from the specified URL
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to download the .torrent from</param>
|
||||
/// <param name="location">The path to download the .torrent to before it gets loaded</param>
|
||||
/// <returns></returns>
|
||||
public static Torrent Load(Uri url, string location)
|
||||
{
|
||||
Check.Url(url);
|
||||
Check.Location(location);
|
||||
|
||||
try
|
||||
{
|
||||
using (WebClient client = new WebClient())
|
||||
client.DownloadFile(url, location);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new TorrentException("Could not download .torrent file from the specified url", ex);
|
||||
}
|
||||
|
||||
return Torrent.Load(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent from the specificed path. A return value indicates
|
||||
/// whether the operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to load the .torrent file from</param>
|
||||
/// <param name="torrent">If the loading was succesful it is assigned the Torrent</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public static bool TryLoad(string path, out Torrent torrent)
|
||||
{
|
||||
Check.Path(path);
|
||||
|
||||
try
|
||||
{
|
||||
torrent = Torrent.Load(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
torrent = null;
|
||||
}
|
||||
|
||||
return torrent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent from the specified byte[]. A return value indicates
|
||||
/// whether the operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte[] to load the .torrent from</param>
|
||||
/// <param name="torrent">If loading was successful, it contains the Torrent</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public static bool TryLoad(byte[] data, out Torrent torrent)
|
||||
{
|
||||
Check.Data(data);
|
||||
|
||||
try
|
||||
{
|
||||
torrent = Torrent.Load(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
torrent = null;
|
||||
}
|
||||
|
||||
return torrent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent from the supplied stream. A return value indicates
|
||||
/// whether the operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the data to load</param>
|
||||
/// <param name="torrent">If the loading was succesful it is assigned the Torrent</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public static bool TryLoad(Stream stream, out Torrent torrent)
|
||||
{
|
||||
Check.Stream(stream);
|
||||
|
||||
try
|
||||
{
|
||||
torrent = Torrent.Load(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
torrent = null;
|
||||
}
|
||||
|
||||
return torrent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a .torrent file from the specified URL. A return value indicates
|
||||
/// whether the operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to download the .torrent from</param>
|
||||
/// <param name="location">The path to download the .torrent to before it gets loaded</param>
|
||||
/// <param name="torrent">If the loading was succesful it is assigned the Torrent</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public static bool TryLoad(Uri url, string location, out Torrent torrent)
|
||||
{
|
||||
Check.Url(url);
|
||||
Check.Location(location);
|
||||
|
||||
try
|
||||
{
|
||||
torrent = Torrent.Load(url, location);
|
||||
}
|
||||
catch
|
||||
{
|
||||
torrent = null;
|
||||
}
|
||||
|
||||
return torrent != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from either Load(stream) or Load(string).
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
private static Torrent Load(Stream stream, string path)
|
||||
{
|
||||
Check.Stream(stream);
|
||||
Check.Path(path);
|
||||
|
||||
try
|
||||
{
|
||||
Torrent t = Torrent.LoadCore((BEncodedDictionary)BEncodedDictionary.Decode(stream));
|
||||
t.torrentPath = path;
|
||||
return t;
|
||||
}
|
||||
catch (BEncodingException ex)
|
||||
{
|
||||
throw new TorrentException("Invalid torrent file specified", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Torrent Load(BEncodedDictionary torrentInformation)
|
||||
{
|
||||
return LoadCore((BEncodedDictionary)BEncodedValue.Decode(torrentInformation.Encode()));
|
||||
}
|
||||
|
||||
internal static Torrent LoadCore(BEncodedDictionary torrentInformation)
|
||||
{
|
||||
Check.TorrentInformation(torrentInformation);
|
||||
|
||||
Torrent t = new Torrent();
|
||||
t.LoadInternal(torrentInformation);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
protected void LoadInternal(BEncodedDictionary torrentInformation)
|
||||
{
|
||||
Check.TorrentInformation(torrentInformation);
|
||||
this.originalDictionary = torrentInformation;
|
||||
this.torrentPath = "";
|
||||
|
||||
try
|
||||
{
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in torrentInformation)
|
||||
{
|
||||
switch (keypair.Key.Text)
|
||||
{
|
||||
case ("announce"):
|
||||
// Ignore this if we have an announce-list
|
||||
if (torrentInformation.ContainsKey("announce-list"))
|
||||
break;
|
||||
this.announceUrls.Add(new RawTrackerTier());
|
||||
this.announceUrls[0].Add(keypair.Value.ToString());
|
||||
break;
|
||||
|
||||
case ("creation date"):
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
this.creationDate = this.creationDate.AddSeconds(long.Parse(keypair.Value.ToString()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentOutOfRangeException)
|
||||
this.creationDate = this.creationDate.AddMilliseconds(long.Parse(keypair.Value.ToString()));
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentOutOfRangeException)
|
||||
throw new BEncodingException("Argument out of range exception when adding seconds to creation date.", e);
|
||||
else if (e is FormatException)
|
||||
throw new BEncodingException(String.Format("Could not parse {0} into a number", keypair.Value), e);
|
||||
else
|
||||
throw;
|
||||
}
|
||||
break;
|
||||
|
||||
case ("nodes"):
|
||||
if (keypair.Value.ToString().Length != 0)
|
||||
this.nodes = (BEncodedList)keypair.Value;
|
||||
break;
|
||||
|
||||
case ("comment.utf-8"):
|
||||
if (keypair.Value.ToString().Length != 0)
|
||||
this.comment = keypair.Value.ToString(); // Always take the UTF-8 version
|
||||
break; // even if there's an existing value
|
||||
|
||||
case ("comment"):
|
||||
if (String.IsNullOrEmpty(this.comment))
|
||||
this.comment = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("publisher-url.utf-8"): // Always take the UTF-8 version
|
||||
this.publisherUrl = keypair.Value.ToString(); // even if there's an existing value
|
||||
break;
|
||||
|
||||
case ("publisher-url"):
|
||||
if (String.IsNullOrEmpty(this.publisherUrl))
|
||||
this.publisherUrl = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("azureus_properties"):
|
||||
this.azureusProperties = keypair.Value;
|
||||
break;
|
||||
|
||||
case ("created by"):
|
||||
this.createdBy = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("encoding"):
|
||||
this.encoding = keypair.Value.ToString();
|
||||
break;
|
||||
|
||||
case ("info"):
|
||||
using (SHA1 s = HashAlgoFactory.Create<SHA1>())
|
||||
this.infoHash = new InfoHash(s.ComputeHash(keypair.Value.Encode()));
|
||||
this.ProcessInfo(((BEncodedDictionary)keypair.Value));
|
||||
break;
|
||||
|
||||
case ("name"): // Handled elsewhere
|
||||
break;
|
||||
|
||||
case ("announce-list"):
|
||||
if (keypair.Value is BEncodedString)
|
||||
break;
|
||||
BEncodedList announces = (BEncodedList)keypair.Value;
|
||||
|
||||
for (int j = 0; j < announces.Count; j++)
|
||||
{
|
||||
if (announces[j] is BEncodedList)
|
||||
{
|
||||
BEncodedList bencodedTier = (BEncodedList)announces[j];
|
||||
List<string> tier = new List<string>(bencodedTier.Count);
|
||||
|
||||
for (int k = 0; k < bencodedTier.Count; k++)
|
||||
tier.Add(bencodedTier[k].ToString());
|
||||
|
||||
Toolbox.Randomize<string>(tier);
|
||||
|
||||
RawTrackerTier collection = new RawTrackerTier();
|
||||
for (int k = 0; k < tier.Count; k++)
|
||||
collection.Add(tier[k]);
|
||||
|
||||
if (collection.Count != 0)
|
||||
this.announceUrls.Add(collection);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BEncodingException(String.Format("Non-BEncodedList found in announce-list (found {0})",
|
||||
announces[j].GetType()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ("httpseeds"):
|
||||
// This form of web-seeding is not supported.
|
||||
break;
|
||||
|
||||
case ("url-list"):
|
||||
if (keypair.Value is BEncodedString)
|
||||
{
|
||||
this.getRightHttpSeeds.Add(((BEncodedString)keypair.Value).Text);
|
||||
}
|
||||
else if (keypair.Value is BEncodedList)
|
||||
{
|
||||
foreach (BEncodedString str in (BEncodedList)keypair.Value)
|
||||
this.GetRightHttpSeeds.Add(str.Text);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is BEncodingException)
|
||||
throw;
|
||||
else
|
||||
throw new BEncodingException("", e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Loading methods
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
[Serializable]
|
||||
public class TorrentException : Exception
|
||||
{
|
||||
public TorrentException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public TorrentException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TorrentException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public TorrentException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the base class for the files available to download from within a .torrent.
|
||||
/// This should be inherited by both Client and Tracker "TorrentFile" classes
|
||||
/// </summary>
|
||||
public class TorrentFile : IEquatable<TorrentFile>
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private BitField bitfield;
|
||||
private BitField selector;
|
||||
private byte[] ed2k;
|
||||
private int endPiece;
|
||||
private string fullPath;
|
||||
private long length;
|
||||
private byte[] md5;
|
||||
private string path;
|
||||
private Priority priority;
|
||||
private byte[] sha1;
|
||||
private int startPiece;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The number of pieces which have been successfully downloaded which are from this file
|
||||
/// </summary>
|
||||
public BitField BitField
|
||||
{
|
||||
get { return this.bitfield; }
|
||||
}
|
||||
|
||||
public long BytesDownloaded
|
||||
{
|
||||
get { return (long)(this.BitField.PercentComplete * this.Length / 100.0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ED2K hash of the file
|
||||
/// </summary>
|
||||
public byte[] ED2K
|
||||
{
|
||||
get { return this.ed2k; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of the last piece of this file
|
||||
/// </summary>
|
||||
public int EndPieceIndex
|
||||
{
|
||||
get { return this.endPiece; }
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
{
|
||||
get { return this.fullPath; }
|
||||
internal set { this.fullPath = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length of the file in bytes
|
||||
/// </summary>
|
||||
public long Length
|
||||
{
|
||||
get { return this.length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MD5 hash of the file
|
||||
/// </summary>
|
||||
public byte[] MD5
|
||||
{
|
||||
get { return this.md5; }
|
||||
internal set { this.md5 = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the case of a single torrent file, this is the name of the file.
|
||||
/// In the case of a multi-file torrent this is the relative path of the file
|
||||
/// (including the filename) from the base directory
|
||||
/// </summary>
|
||||
public string Path
|
||||
{
|
||||
get { return this.path; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The priority of this torrent file
|
||||
/// </summary>
|
||||
public Priority Priority
|
||||
{
|
||||
get { return this.priority; }
|
||||
set { this.priority = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SHA1 hash of the file
|
||||
/// </summary>
|
||||
public byte[] SHA1
|
||||
{
|
||||
get { return this.sha1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of the first piece of this file
|
||||
/// </summary>
|
||||
public int StartPieceIndex
|
||||
{
|
||||
get { return this.startPiece; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public TorrentFile(string path, long length)
|
||||
: this(path, length, path)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TorrentFile(string path, long length, string fullPath)
|
||||
: this(path, length, fullPath, 0, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TorrentFile(string path, long length, int startIndex, int endIndex)
|
||||
: this(path, length, path, startIndex, endIndex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TorrentFile(string path, long length, string fullPath, int startIndex, int endIndex)
|
||||
: this(path, length, fullPath, startIndex, endIndex, null, null, null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TorrentFile(string path, long length, string fullPath, int startIndex, int endIndex, byte[] md5, byte[] ed2k, byte[] sha1)
|
||||
{
|
||||
this.bitfield = new BitField(endIndex - startIndex + 1);
|
||||
this.ed2k = ed2k;
|
||||
this.endPiece = endIndex;
|
||||
this.fullPath = fullPath;
|
||||
this.length = length;
|
||||
this.md5 = md5;
|
||||
this.path = path;
|
||||
this.priority = Priority.Normal;
|
||||
this.sha1 = sha1;
|
||||
this.startPiece = startIndex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as TorrentFile);
|
||||
}
|
||||
|
||||
public bool Equals(TorrentFile other)
|
||||
{
|
||||
return other == null ? false : this.path == other.path && this.length == other.length; ;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.path.GetHashCode();
|
||||
}
|
||||
|
||||
internal BitField GetSelector(int totalPieces)
|
||||
{
|
||||
if (this.selector != null)
|
||||
return this.selector;
|
||||
|
||||
this.selector = new BitField(totalPieces);
|
||||
for (int i = this.StartPieceIndex; i <= this.EndPieceIndex; i++)
|
||||
this.selector[i] = true;
|
||||
return this.selector;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
sb.Append("File: ");
|
||||
sb.Append(this.path);
|
||||
sb.Append(" StartIndex: ");
|
||||
sb.Append(this.StartPieceIndex);
|
||||
sb.Append(" EndIndex: ");
|
||||
sb.Append(this.EndPieceIndex);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
//
|
||||
// System.Web.HttpUtility/HttpEncoder
|
||||
//
|
||||
// Authors:
|
||||
// Patrik Torstensson (Patrik.Torstensson@labs2.com)
|
||||
// Wictor Wilén (decode/encode functions) (wictor@ibizkit.se)
|
||||
// Tim Coleman (tim@timcoleman.com)
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MonoTorrent
|
||||
{
|
||||
static class UriHelper
|
||||
{
|
||||
static readonly char[] hexChars = "0123456789abcdef".ToCharArray();
|
||||
|
||||
public static string UrlEncode(byte[] bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
throw new ArgumentNullException("bytes");
|
||||
|
||||
var result = new MemoryStream(bytes.Length);
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
UrlEncodeChar((char)bytes[i], result, false);
|
||||
|
||||
return Encoding.ASCII.GetString(result.ToArray());
|
||||
}
|
||||
|
||||
public static byte[] UrlDecode(string s)
|
||||
{
|
||||
if (null == s)
|
||||
return null;
|
||||
|
||||
var e = Encoding.UTF8;
|
||||
if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
|
||||
return e.GetBytes(s);
|
||||
|
||||
long len = s.Length;
|
||||
var bytes = new List<byte>();
|
||||
int xchar;
|
||||
char ch;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
ch = s[i];
|
||||
if (ch == '%' && i + 2 < len && s[i + 1] != '%')
|
||||
{
|
||||
if (s[i + 1] == 'u' && i + 5 < len)
|
||||
{
|
||||
// unicode hex sequence
|
||||
xchar = GetChar(s, i + 2, 4);
|
||||
if (xchar != -1)
|
||||
{
|
||||
WriteCharBytes(bytes, (char)xchar, e);
|
||||
i += 5;
|
||||
}
|
||||
else
|
||||
WriteCharBytes(bytes, '%', e);
|
||||
}
|
||||
else if ((xchar = GetChar(s, i + 1, 2)) != -1)
|
||||
{
|
||||
WriteCharBytes(bytes, (char)xchar, e);
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteCharBytes(bytes, '%', e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '+')
|
||||
WriteCharBytes(bytes, ' ', e);
|
||||
else
|
||||
WriteCharBytes(bytes, ch, e);
|
||||
}
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
static void UrlEncodeChar(char c, Stream result, bool isUnicode)
|
||||
{
|
||||
if (c > ' ' && NotEncoded(c))
|
||||
{
|
||||
result.WriteByte((byte)c);
|
||||
return;
|
||||
}
|
||||
if (c == ' ')
|
||||
{
|
||||
result.WriteByte((byte)'+');
|
||||
return;
|
||||
}
|
||||
if ((c < '0') ||
|
||||
(c < 'A' && c > '9') ||
|
||||
(c > 'Z' && c < 'a') ||
|
||||
(c > 'z'))
|
||||
{
|
||||
if (isUnicode && c > 127)
|
||||
{
|
||||
result.WriteByte((byte)'%');
|
||||
result.WriteByte((byte)'u');
|
||||
result.WriteByte((byte)'0');
|
||||
result.WriteByte((byte)'0');
|
||||
}
|
||||
else
|
||||
result.WriteByte((byte)'%');
|
||||
|
||||
int idx = ((int)c) >> 4;
|
||||
result.WriteByte((byte)hexChars[idx]);
|
||||
idx = ((int)c) & 0x0F;
|
||||
result.WriteByte((byte)hexChars[idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.WriteByte((byte)c);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetChar(string str, int offset, int length)
|
||||
{
|
||||
int val = 0;
|
||||
int end = length + offset;
|
||||
for (int i = offset; i < end; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if (c > 127)
|
||||
return -1;
|
||||
|
||||
int current = GetInt((byte)c);
|
||||
if (current == -1)
|
||||
return -1;
|
||||
val = (val << 4) + current;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int GetInt(byte b)
|
||||
{
|
||||
char c = (char)b;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool NotEncoded(char c)
|
||||
{
|
||||
return c == '!' || c == '(' || c == ')' || c == '*' || c == '-' || c == '.' || c == '_' || c == '\'';
|
||||
}
|
||||
|
||||
static void WriteCharBytes(List<byte> buf, char ch, Encoding e)
|
||||
{
|
||||
if (ch > 255)
|
||||
{
|
||||
foreach (byte b in e.GetBytes(new char[] { ch }))
|
||||
buf.Add(b);
|
||||
}
|
||||
else
|
||||
buf.Add((byte)ch);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -207,7 +207,7 @@ namespace NzbDrone.Core.Download
|
|||
|
||||
try
|
||||
{
|
||||
hash = new MagnetLink(magnetUrl).InfoHash.ToHex();
|
||||
hash = InfoHash.FromMagnetLink(magnetUrl).ToHex();
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using MonoTorrent;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
@ -51,8 +52,7 @@ namespace NzbDrone.Core.Indexers
|
|||
{
|
||||
try
|
||||
{
|
||||
var magnetLink = new MonoTorrent.MagnetLink(magnetUrl);
|
||||
return magnetLink.InfoHash.ToHex();
|
||||
return InfoHash.FromMagnetLink(magnetUrl).ToHex();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
<PackageReference Include="Kveer.XmlRPC" Version="1.1.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Lidarr" Version="1.0.111.0-5" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.0" />
|
||||
<PackageReference Include="MonoTorrent" Version="1.0.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MonoTorrent\MonoTorrent.csproj" />
|
||||
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||
|
|
|
@ -11,8 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsServiceHelpers", "Wi
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{486ADF86-DD89-4E19-B805-9D94F19800D9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{0F0D4998-8F5D-4467-A909-BB192C4B3B4B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{4EACDBBC-BCD7-4765-A57B-3E08331E4749}"
|
||||
|
@ -23,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Api.V3", "Radarr.Api
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Http", "Radarr.Http\Radarr.Http.csproj", "{F8A02FD4-A7A4-40D0-BB81-6319105A3302}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoTorrent", "MonoTorrent\MonoTorrent.csproj", "{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Api.Test", "NzbDrone.Api.Test\Radarr.Api.Test.csproj", "{E2EA47B1-6996-417D-A6EC-28C4F202715C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Automation.Test", "NzbDrone.Automation.Test\Radarr.Automation.Test.csproj", "{2356C987-F992-4084-9DA2-5DAD1DA35E85}"
|
||||
|
@ -103,14 +99,6 @@ Global
|
|||
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Posix.Build.0 = Release|Any CPU
|
||||
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Windows.ActiveCfg = Release|Any CPU
|
||||
{F8A02FD4-A7A4-40D0-BB81-6319105A3302}.Release|Windows.Build.0 = Release|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Posix.ActiveCfg = Debug|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Posix.Build.0 = Debug|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Windows.ActiveCfg = Debug|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Debug|Windows.Build.0 = Debug|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Release|Posix.ActiveCfg = Release|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Release|Posix.Build.0 = Release|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Release|Windows.ActiveCfg = Release|Any CPU
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80}.Release|Windows.Build.0 = Release|Any CPU
|
||||
{E2EA47B1-6996-417D-A6EC-28C4F202715C}.Debug|Posix.ActiveCfg = Debug|Any CPU
|
||||
{E2EA47B1-6996-417D-A6EC-28C4F202715C}.Debug|Posix.Build.0 = Debug|Any CPU
|
||||
{E2EA47B1-6996-417D-A6EC-28C4F202715C}.Debug|Windows.ActiveCfg = Debug|Any CPU
|
||||
|
@ -300,7 +288,6 @@ Global
|
|||
GlobalSection(NestedProjects) = preSolution
|
||||
{47697CDB-27B6-4B05-B4F8-0CBE6F6EDF97} = {57A04B72-8088-4F75-A582-1158CF8291F7}
|
||||
{4EACDBBC-BCD7-4765-A57B-3E08331E4749} = {57A04B72-8088-4F75-A582-1158CF8291F7}
|
||||
{BE8533CC-A1ED-46A6-811F-2FA29CC6AD80} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
|
||||
{E2EA47B1-6996-417D-A6EC-28C4F202715C} = {57A04B72-8088-4F75-A582-1158CF8291F7}
|
||||
{2356C987-F992-4084-9DA2-5DAD1DA35E85} = {57A04B72-8088-4F75-A582-1158CF8291F7}
|
||||
{A628FEA4-75CC-4039-8823-27258C55D2BF} = {57A04B72-8088-4F75-A582-1158CF8291F7}
|
||||
|
|
Loading…
Reference in New Issue