mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交Unity 联机Pro
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
//
|
||||
// System.IO.MemoryStream.cs
|
||||
//
|
||||
// Authors: Marcin Szczepanski (marcins@zipworld.com.au)
|
||||
// Patrik Torstensson
|
||||
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
||||
//
|
||||
// (c) 2001,2002 Marcin Szczepanski, Patrik Torstensson
|
||||
// (c) 2003 Ximian, Inc. (http://www.ximian.com)
|
||||
// Copyright (C) 2004 Novell (http://www.novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a modified MemoryStream class to use VariableSizedBufferPool
|
||||
/// </summary>
|
||||
public sealed class BufferPoolMemoryStream : System.IO.Stream
|
||||
{
|
||||
bool canWrite;
|
||||
bool allowGetBuffer;
|
||||
int capacity;
|
||||
int length;
|
||||
byte[] internalBuffer;
|
||||
int initialIndex;
|
||||
bool expandable;
|
||||
bool streamClosed;
|
||||
int position;
|
||||
int dirty_bytes;
|
||||
bool releaseInternalBuffer;
|
||||
|
||||
public BufferPoolMemoryStream() : this(0)
|
||||
{
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(int capacity)
|
||||
{
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException("capacity");
|
||||
|
||||
canWrite = true;
|
||||
|
||||
//internalBuffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
|
||||
//this.capacity = internalBuffer.Length;
|
||||
//
|
||||
//expandable = true;
|
||||
//allowGetBuffer = true;
|
||||
var buffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
|
||||
InternalConstructor(buffer, 0, buffer.Length, true, true, true, true);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
InternalConstructor(buffer, 0, buffer.Length, true, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, bool writable)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
InternalConstructor(buffer, 0, buffer.Length, writable, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, true, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, false, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, true, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, false);
|
||||
}
|
||||
|
||||
public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer, bool canExpand)
|
||||
{
|
||||
InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, canExpand);
|
||||
}
|
||||
|
||||
void InternalConstructor(byte[] buffer, int index, int count, bool writable, bool publicallyVisible, bool releaseBuffer, bool canExpand)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (index < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException("index or count is less than 0.");
|
||||
|
||||
if (buffer.Length - index < count)
|
||||
throw new ArgumentException("index+count",
|
||||
"The size of the buffer is less than index + count.");
|
||||
|
||||
canWrite = writable;
|
||||
|
||||
internalBuffer = buffer;
|
||||
capacity = count + index;
|
||||
//length = capacity;
|
||||
length = 0;
|
||||
position = index;
|
||||
initialIndex = index;
|
||||
|
||||
allowGetBuffer = publicallyVisible;
|
||||
releaseInternalBuffer = releaseBuffer;
|
||||
expandable = canExpand;
|
||||
}
|
||||
|
||||
void CheckIfClosedThrowDisposed()
|
||||
{
|
||||
if (streamClosed)
|
||||
throw new ObjectDisposedException("MemoryStream");
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return !streamClosed; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return !streamClosed; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return (!streamClosed && canWrite); }
|
||||
}
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
return capacity - initialIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (value == capacity)
|
||||
return; // LAMENESS: see MemoryStreamTest.ConstructorFive
|
||||
|
||||
if (!expandable)
|
||||
throw new NotSupportedException("Cannot expand this MemoryStream");
|
||||
|
||||
if (value < 0 || value < length)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
|
||||
|
||||
byte[] newBuffer = null;
|
||||
if (value != 0)
|
||||
{
|
||||
newBuffer = BufferPool.Get(value, true);
|
||||
Buffer.BlockCopy(internalBuffer, 0, newBuffer, 0, length);
|
||||
}
|
||||
|
||||
dirty_bytes = 0; // discard any dirty area beyond previous length
|
||||
BufferPool.Release(internalBuffer);
|
||||
internalBuffer = newBuffer; // It's null when capacity is set to 0
|
||||
capacity = internalBuffer != null ? internalBuffer.Length : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
// LAMESPEC: The spec says to throw an IOException if the
|
||||
// stream is closed and an ObjectDisposedException if
|
||||
// "methods were called after the stream was closed". What
|
||||
// is the difference?
|
||||
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
// This is ok for MemoryStreamTest.ConstructorFive
|
||||
return length - initialIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
return position - initialIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Position cannot be negative");
|
||||
|
||||
if (value > Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Position must be non-negative and less than 2^31 - 1 - origin");
|
||||
|
||||
position = initialIndex + (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
streamClosed = true;
|
||||
expandable = false;
|
||||
if (disposing && internalBuffer != null && this.releaseInternalBuffer)
|
||||
BufferPool.Release(internalBuffer);
|
||||
internalBuffer = null;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public byte[] GetBuffer()
|
||||
{
|
||||
if (!allowGetBuffer)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
return internalBuffer;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException("offset or count less than zero.");
|
||||
|
||||
if (buffer.Length - offset < count)
|
||||
throw new ArgumentException("offset+count",
|
||||
"The size of the buffer is less than offset + count.");
|
||||
|
||||
if (position >= length || count == 0)
|
||||
return 0;
|
||||
|
||||
if (position > length - count)
|
||||
count = length - position;
|
||||
|
||||
Buffer.BlockCopy(internalBuffer, position, buffer, offset, count);
|
||||
position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (position >= length)
|
||||
return -1;
|
||||
|
||||
return internalBuffer[position++];
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin loc)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
// It's funny that they don't throw this exception for < Int32.MinValue
|
||||
if (offset > (long)Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException("Offset out of range. " + offset);
|
||||
|
||||
int refPoint;
|
||||
switch (loc)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
if (offset < 0)
|
||||
throw new IOException("Attempted to seek before start of MemoryStream.");
|
||||
refPoint = initialIndex;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
refPoint = position;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
refPoint = length;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("loc", "Invalid SeekOrigin");
|
||||
}
|
||||
|
||||
// LAMESPEC: My goodness, how may LAMESPECs are there in this
|
||||
// class! :) In the spec for the Position property it's stated
|
||||
// "The position must not be more than one byte beyond the end of the stream."
|
||||
// In the spec for seek it says "Seeking to any location beyond the length of the
|
||||
// stream is supported." That's a contradiction i'd say.
|
||||
// I guess seek can go anywhere but if you use position it may get moved back.
|
||||
|
||||
refPoint += (int)offset;
|
||||
if (refPoint < initialIndex)
|
||||
throw new IOException("Attempted to seek before start of MemoryStream.");
|
||||
|
||||
position = refPoint;
|
||||
return position;
|
||||
}
|
||||
|
||||
int CalculateNewCapacity(int minimum)
|
||||
{
|
||||
if (minimum < 256)
|
||||
minimum = 256; // See GetBufferTwo test
|
||||
|
||||
if (minimum < capacity * 2)
|
||||
minimum = capacity * 2;
|
||||
|
||||
if (!UnityEngine.Mathf.IsPowerOfTwo(minimum))
|
||||
minimum = UnityEngine.Mathf.NextPowerOfTwo(minimum);
|
||||
|
||||
return minimum;
|
||||
}
|
||||
|
||||
void Expand(int newSize)
|
||||
{
|
||||
// We don't need to take into account the dirty bytes when incrementing the
|
||||
// Capacity, as changing it will only preserve the valid clear region.
|
||||
if (newSize > capacity)
|
||||
Capacity = CalculateNewCapacity(newSize);
|
||||
else if (dirty_bytes > 0)
|
||||
{
|
||||
Array.Clear(internalBuffer, length, dirty_bytes);
|
||||
dirty_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
if (!expandable && value > capacity)
|
||||
throw new NotSupportedException("Expanding this MemoryStream is not supported");
|
||||
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (!canWrite)
|
||||
{
|
||||
throw new NotSupportedException("Cannot write to this MemoryStream");
|
||||
}
|
||||
|
||||
// LAMESPEC: AGAIN! It says to throw this exception if value is
|
||||
// greater than "the maximum length of the MemoryStream". I haven't
|
||||
// seen anywhere mention what the maximum length of a MemoryStream is and
|
||||
// since we're this far this memory stream is expandable.
|
||||
if (value < 0 || (value + initialIndex) > (long)Int32.MaxValue)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
int newSize = (int)value + initialIndex;
|
||||
|
||||
if (newSize > length)
|
||||
Expand(newSize);
|
||||
else if (newSize < length) // Postpone the call to Array.Clear till expand time
|
||||
dirty_bytes += length - newSize;
|
||||
|
||||
length = newSize;
|
||||
if (position > length)
|
||||
position = length;
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
return ToArray(false);
|
||||
}
|
||||
|
||||
public byte[] ToArray(bool canBeLarger)
|
||||
{
|
||||
int l = length - initialIndex;
|
||||
byte[] outBuffer = null;
|
||||
|
||||
if (l > 0)
|
||||
{
|
||||
if (canBeLarger)
|
||||
outBuffer = BufferPool.Get(l, true);
|
||||
else
|
||||
outBuffer = new byte[l];
|
||||
}
|
||||
else
|
||||
{
|
||||
outBuffer = BufferPool.NoData;
|
||||
}
|
||||
|
||||
if (internalBuffer != null)
|
||||
Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
public BufferSegment ToBufferSegment()
|
||||
{
|
||||
int l = length - initialIndex;
|
||||
byte[] outBuffer = l > 0 ? BufferPool.Get(l, true) : BufferPool.NoData;
|
||||
|
||||
if (internalBuffer != null)
|
||||
Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
|
||||
|
||||
return new BufferSegment(outBuffer, 0, l);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (!canWrite)
|
||||
throw new NotSupportedException("Cannot write to this stream.");
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || count < 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
if (buffer.Length - offset < count)
|
||||
throw new ArgumentException("offset+count",
|
||||
"The size of the buffer is less than offset + count.");
|
||||
|
||||
// reordered to avoid possible integer overflow
|
||||
if (position > length - count)
|
||||
Expand(position + count);
|
||||
|
||||
Buffer.BlockCopy(buffer, offset, internalBuffer, position, count);
|
||||
position += count;
|
||||
if (position >= length)
|
||||
length = position;
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
if (!canWrite)
|
||||
throw new NotSupportedException("Cannot write to this stream.");
|
||||
|
||||
if (position >= length)
|
||||
{
|
||||
Expand(position + 1);
|
||||
length = position + 1;
|
||||
}
|
||||
|
||||
internalBuffer[position++] = value;
|
||||
}
|
||||
|
||||
public void WriteTo(Stream stream)
|
||||
{
|
||||
CheckIfClosedThrowDisposed();
|
||||
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
stream.Write(internalBuffer, initialIndex, length - initialIndex);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce34f1f3b5c30794b9e26c0922185261
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public class BufferSegmentStream : Stream
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
|
||||
public override bool CanSeek { get { return false; } }
|
||||
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length { get { return this._length; } }
|
||||
protected long _length;
|
||||
|
||||
public override long Position { get { return 0; } set { } }
|
||||
|
||||
protected List<BufferSegment> bufferList = new List<BufferSegment>();
|
||||
|
||||
private byte[] _tempByteArray = new byte[1];
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (Read(this._tempByteArray, 0, 1) == 0)
|
||||
return -1;
|
||||
|
||||
return this._tempByteArray[0];
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int sumReadCount = 0;
|
||||
|
||||
while (count > 0 && bufferList.Count > 0)
|
||||
{
|
||||
BufferSegment buff = this.bufferList[0];
|
||||
|
||||
int readCount = Math.Min(count, buff.Count);
|
||||
|
||||
Array.Copy(buff.Data, buff.Offset, buffer, offset, readCount);
|
||||
|
||||
sumReadCount += readCount;
|
||||
offset += readCount;
|
||||
count -= readCount;
|
||||
|
||||
this.bufferList[0] = buff = buff.Slice(buff.Offset + readCount);
|
||||
|
||||
if (buff.Count == 0)
|
||||
{
|
||||
this.bufferList.RemoveAt(0);
|
||||
BufferPool.Release(buff.Data);
|
||||
}
|
||||
}
|
||||
|
||||
this._length -= sumReadCount;
|
||||
|
||||
return sumReadCount;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Write(new BufferSegment(buffer, offset, count));
|
||||
}
|
||||
|
||||
public virtual void Write(BufferSegment bufferSegment)
|
||||
{
|
||||
this.bufferList.Add(bufferSegment);
|
||||
this._length += bufferSegment.Count;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
for (int i = 0; i < this.bufferList.Count; ++i)
|
||||
BufferPool.Release(this.bufferList[i]);
|
||||
this.bufferList.Clear();
|
||||
this._length = 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 163c4b09e7c509c4aaffd21e9961824c
|
||||
timeCreated: 1574255448
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public sealed class CircularBuffer<T>
|
||||
{
|
||||
public int Capacity { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public int StartIdx { get { return this.startIdx; } }
|
||||
public int EndIdx { get { return this.endIdx; } }
|
||||
|
||||
public T this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
int realIdx = (this.startIdx + idx) % this.Capacity;
|
||||
|
||||
return this.buffer[realIdx];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int realIdx = (this.startIdx + idx) % this.Capacity;
|
||||
|
||||
this.buffer[realIdx] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private T[] buffer;
|
||||
private int startIdx;
|
||||
private int endIdx;
|
||||
|
||||
public CircularBuffer(int capacity)
|
||||
{
|
||||
this.Capacity = capacity;
|
||||
}
|
||||
|
||||
public void Add(T element)
|
||||
{
|
||||
if (this.buffer == null)
|
||||
this.buffer = new T[this.Capacity];
|
||||
|
||||
this.buffer[this.endIdx] = element;
|
||||
|
||||
this.endIdx = (this.endIdx + 1) % this.Capacity;
|
||||
if (this.endIdx == this.startIdx)
|
||||
this.startIdx = (this.startIdx + 1) % this.Capacity;
|
||||
|
||||
this.Count = Math.Min(this.Count + 1, this.Capacity);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.Count = this.startIdx = this.endIdx = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = PlatformSupport.Text.StringBuilderPool.Get(2);
|
||||
sb.Append("[");
|
||||
|
||||
int idx = this.startIdx;
|
||||
while (idx != this.endIdx)
|
||||
{
|
||||
sb.Append(this.buffer[idx].ToString());
|
||||
|
||||
idx = (idx + 1) % this.Capacity;
|
||||
if (idx != this.endIdx)
|
||||
sb.Append("; ");
|
||||
}
|
||||
sb.Append("]");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e42ed97af74b54fba27235416b1191
|
||||
timeCreated: 1508423928
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,519 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BestHTTP.PlatformSupport.Text;
|
||||
|
||||
#if NETFX_CORE
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
#else
|
||||
using Cryptography = System.Security.Cryptography;
|
||||
using FileStream = System.IO.FileStream;
|
||||
#endif
|
||||
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
#region ASCII Encoding (These are required because Windows Phone doesn't supports the Encoding.ASCII class.)
|
||||
|
||||
/// <summary>
|
||||
/// On WP8 platform there are no ASCII encoding.
|
||||
/// </summary>
|
||||
public static string AsciiToString(this byte[] bytes)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Get(bytes.Length); //new StringBuilder(bytes.Length);
|
||||
foreach (byte b in bytes)
|
||||
sb.Append(b <= 0x7f ? (char)b : '?');
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On WP8 platform there are no ASCII encoding.
|
||||
/// </summary>
|
||||
public static BufferSegment GetASCIIBytes(this string str)
|
||||
{
|
||||
byte[] result = BufferPool.Get(str.Length, true);
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
result[i] = (byte)((ch < (char)0x80) ? ch : '?');
|
||||
}
|
||||
|
||||
return new BufferSegment(result, 0, str.Length);
|
||||
}
|
||||
|
||||
public static void SendAsASCII(this BinaryWriter stream, string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
|
||||
stream.Write((byte)((ch < (char)0x80) ? ch : '?'));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FileSystem WriteLine function support
|
||||
|
||||
public static void WriteLine(this Stream fs)
|
||||
{
|
||||
fs.Write(HTTPRequest.EOL, 0, 2);
|
||||
}
|
||||
|
||||
public static void WriteLine(this Stream fs, string line)
|
||||
{
|
||||
var buff = line.GetASCIIBytes();
|
||||
fs.Write(buff.Data, buff.Offset, buff.Count);
|
||||
fs.WriteLine();
|
||||
BufferPool.Release(buff);
|
||||
}
|
||||
|
||||
public static void WriteLine(this Stream fs, string format, params object[] values)
|
||||
{
|
||||
var buff = string.Format(format, values).GetASCIIBytes();
|
||||
fs.Write(buff.Data, buff.Offset, buff.Count);
|
||||
fs.WriteLine();
|
||||
BufferPool.Release(buff);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Other Extensions
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes)
|
||||
{
|
||||
return new BufferSegment(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes, int length)
|
||||
{
|
||||
return new BufferSegment(bytes, 0, length);
|
||||
}
|
||||
|
||||
public static BufferSegment AsBuffer(this byte[] bytes, int offset, int length)
|
||||
{
|
||||
return new BufferSegment(bytes, offset, length);
|
||||
}
|
||||
|
||||
public static string GetRequestPathAndQueryURL(this Uri uri)
|
||||
{
|
||||
string requestPathAndQuery = uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);
|
||||
|
||||
// http://forum.unity3d.com/threads/best-http-released.200006/page-26#post-2723250
|
||||
if (string.IsNullOrEmpty(requestPathAndQuery))
|
||||
requestPathAndQuery = "/";
|
||||
|
||||
return requestPathAndQuery;
|
||||
}
|
||||
|
||||
public static string[] FindOption(this string str, string option)
|
||||
{
|
||||
//s-maxage=2678400, must-revalidate, max-age=0
|
||||
string[] options = str.ToLowerInvariant().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
option = option.ToLowerInvariant();
|
||||
|
||||
for (int i = 0; i < options.Length; ++i)
|
||||
if (options[i].Contains(option))
|
||||
return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string[] FindOption(this string[] options, string option)
|
||||
{
|
||||
for (int i = 0; i < options.Length; ++i)
|
||||
if (options[i].Contains(option))
|
||||
return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteArray(this Stream stream, byte[] array)
|
||||
{
|
||||
stream.Write(array, 0, array.Length);
|
||||
}
|
||||
|
||||
public static void WriteBufferSegment(this Stream stream, BufferSegment buffer)
|
||||
{
|
||||
stream.Write(buffer.Data, buffer.Offset, buffer.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Uri's host is a valid IPv4 or IPv6 address.
|
||||
/// </summary>
|
||||
public static bool IsHostIsAnIPAddress(this Uri uri)
|
||||
{
|
||||
if (uri == null)
|
||||
return false;
|
||||
|
||||
return IsIpV4AddressValid(uri.Host) || IsIpV6AddressValid(uri.Host);
|
||||
}
|
||||
|
||||
// Original idea from: https://www.code4copy.com/csharp/c-validate-ip-address-string/
|
||||
// Working regex: https://www.regular-expressions.info/ip.html
|
||||
private static readonly System.Text.RegularExpressions.Regex validIpV4AddressRegex = new System.Text.RegularExpressions.Regex("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an IPv4 address.
|
||||
/// </summary>
|
||||
public static bool IsIpV4AddressValid(string address)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
return validIpV4AddressRegex.IsMatch(address.Trim());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an IPv6 address.
|
||||
/// </summary>
|
||||
public static bool IsIpV6AddressValid(string address)
|
||||
{
|
||||
#if !NETFX_CORE
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
{
|
||||
System.Net.IPAddress ip;
|
||||
if (System.Net.IPAddress.TryParse(address, out ip))
|
||||
return ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String Conversions
|
||||
|
||||
public static int ToInt32(this string str, int defaultValue = default(int))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static long ToInt64(this string str, long defaultValue = default(long))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
return long.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime ToDateTime(this string str, DateTime defaultValue = default(DateTime))
|
||||
{
|
||||
if (str == null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
DateTime.TryParse(str, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out defaultValue);
|
||||
return defaultValue.ToUniversalTime();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToStrOrEmpty(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
return String.Empty;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ToStr(this string str, string defaultVale)
|
||||
{
|
||||
if (str == null)
|
||||
return defaultVale;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ToBinaryStr(this byte value)
|
||||
{
|
||||
return Convert.ToString(value, 2).PadLeft(8, '0');
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MD5 Hashing
|
||||
|
||||
public static string CalculateMD5Hash(this string input)
|
||||
{
|
||||
var asciiBuff = input.GetASCIIBytes();
|
||||
var hash = asciiBuff.CalculateMD5Hash();
|
||||
BufferPool.Release(asciiBuff);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static string CalculateMD5Hash(this BufferSegment input)
|
||||
{
|
||||
#if NETFX_CORE
|
||||
var alg = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
|
||||
//IBuffer buff = CryptographicBuffer.CreateFromByteArray(input);
|
||||
IBuffer buff = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.AsBuffer(input.Data, input.Offset, input.Count);
|
||||
var hashed = alg.HashData(buff);
|
||||
var res = CryptographicBuffer.EncodeToHexString(hashed);
|
||||
return res;
|
||||
#else
|
||||
using (var md5 = Cryptography.MD5.Create()) {
|
||||
var hash = md5.ComputeHash(input.Data, input.Offset, input.Count);
|
||||
var sb = StringBuilderPool.Get(hash.Length); //new StringBuilder(hash.Length);
|
||||
for (int i = 0; i < hash.Length; ++i)
|
||||
sb.Append(hash[i].ToString("x2"));
|
||||
BufferPool.Release(hash);
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Efficient String Parsing Helpers
|
||||
|
||||
internal static string Read(this string str, ref int pos, char block, bool needResult = true)
|
||||
{
|
||||
return str.Read(ref pos, (ch) => ch != block, needResult);
|
||||
}
|
||||
|
||||
internal static string Read(this string str, ref int pos, Func<char, bool> block, bool needResult = true)
|
||||
{
|
||||
if (pos >= str.Length)
|
||||
return string.Empty;
|
||||
|
||||
str.SkipWhiteSpace(ref pos);
|
||||
|
||||
int startPos = pos;
|
||||
|
||||
while (pos < str.Length && block(str[pos]))
|
||||
pos++;
|
||||
|
||||
string result = needResult ? str.Substring(startPos, pos - startPos) : null;
|
||||
|
||||
// set position to the next char
|
||||
pos++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static string ReadPossibleQuotedText(this string str, ref int pos)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
// It's a quoted text?
|
||||
if (str[pos] == '\"')
|
||||
{
|
||||
// Skip the starting quote
|
||||
str.Read(ref pos, '\"', false);
|
||||
|
||||
// Read the text until the ending quote
|
||||
result = str.Read(ref pos, '\"');
|
||||
|
||||
// Next option
|
||||
str.Read(ref pos, (ch) => ch != ',' && ch != ';', false);
|
||||
}
|
||||
else
|
||||
// It's not a quoted text, so we will read until the next option
|
||||
result = str.Read(ref pos, (ch) => ch != ',' && ch != ';');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void SkipWhiteSpace(this string str, ref int pos)
|
||||
{
|
||||
if (pos >= str.Length)
|
||||
return;
|
||||
|
||||
while (pos < str.Length && char.IsWhiteSpace(str[pos]))
|
||||
pos++;
|
||||
}
|
||||
|
||||
internal static string TrimAndLower(this string str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
char[] buffer = new char[str.Length];
|
||||
int length = 0;
|
||||
|
||||
for (int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char ch = str[i];
|
||||
if (!char.IsWhiteSpace(ch) && !char.IsControl(ch))
|
||||
buffer[length++] = char.ToLowerInvariant(ch);
|
||||
}
|
||||
|
||||
return new string(buffer, 0, length);
|
||||
}
|
||||
|
||||
internal static char? Peek(this string str, int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= str.Length)
|
||||
return null;
|
||||
|
||||
return str[pos];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Specialized String Parsers
|
||||
|
||||
//public, max-age=2592000
|
||||
internal static List<HeaderValue> ParseOptionalHeader(this string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
// process the rest of the text
|
||||
while (idx < str.Length)
|
||||
{
|
||||
// Read key
|
||||
string key = str.Read(ref idx, (ch) => ch != '=' && ch != ',').TrimAndLower();
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
if (str[idx - 1] == '=')
|
||||
qp.Value = str.ReadPossibleQuotedText(ref idx);
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//deflate, gzip, x-gzip, identity, *;q=0
|
||||
internal static List<HeaderValue> ParseQualityParams(this string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str == null)
|
||||
return result;
|
||||
|
||||
int idx = 0;
|
||||
while (idx < str.Length)
|
||||
{
|
||||
string key = str.Read(ref idx, (ch) => ch != ',' && ch != ';').TrimAndLower();
|
||||
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
if (str[idx - 1] == ';')
|
||||
{
|
||||
str.Read(ref idx, '=', false);
|
||||
qp.Value = str.Read(ref idx, ',');
|
||||
}
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Buffer Filling
|
||||
|
||||
/// <summary>
|
||||
/// Will fill the entire buffer from the stream. Will throw an exception when the underlying stream is closed.
|
||||
/// </summary>
|
||||
public static void ReadBuffer(this Stream stream, byte[] buffer)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int read = stream.Read(buffer, count, buffer.Length - count);
|
||||
|
||||
if (read <= 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
count += read;
|
||||
} while (count < buffer.Length);
|
||||
}
|
||||
|
||||
public static void ReadBuffer(this Stream stream, byte[] buffer, int length)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int read = stream.Read(buffer, count, length - count);
|
||||
|
||||
if (read <= 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
count += read;
|
||||
} while (count < length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BufferPoolMemoryStream
|
||||
|
||||
public static void WriteString(this BufferPoolMemoryStream ms, string str)
|
||||
{
|
||||
var byteCount = Encoding.UTF8.GetByteCount(str);
|
||||
byte[] buffer = BufferPool.Get(byteCount, true);
|
||||
Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
|
||||
ms.Write(buffer, 0, byteCount);
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
public static void WriteLine(this BufferPoolMemoryStream ms)
|
||||
{
|
||||
ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
|
||||
}
|
||||
|
||||
public static void WriteLine(this BufferPoolMemoryStream ms, string str)
|
||||
{
|
||||
ms.WriteString(str);
|
||||
ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if NET_STANDARD_2_0 || NETFX_CORE || NET_4_6
|
||||
public static void Clear<T>(this System.Collections.Concurrent.ConcurrentQueue<T> queue)
|
||||
{
|
||||
T result;
|
||||
while (queue.TryDequeue(out result))
|
||||
;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class ExceptionHelper
|
||||
{
|
||||
public static Exception ServerClosedTCPStream()
|
||||
{
|
||||
return new Exception("TCP Stream closed unexpectedly by the remote server");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ed17defed57cb64697daf74b5bb5873
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
409
JNFrame2/Assets/Plugins/Best HTTP/Source/Extensions/Future.cs
Normal file
409
JNFrame2/Assets/Plugins/Best HTTP/Source/Extensions/Future.cs
Normal file
@@ -0,0 +1,409 @@
|
||||
// Based on https://github.com/nickgravelyn/UnityToolbag/blob/master/Future/Future.cs
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017, Nick Gravelyn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
* */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Futures
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the state of a future.
|
||||
/// </summary>
|
||||
public enum FutureState
|
||||
{
|
||||
/// <summary>
|
||||
/// The future hasn't begun to resolve a value.
|
||||
/// </summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// The future is working on resolving a value.
|
||||
/// </summary>
|
||||
Processing,
|
||||
|
||||
/// <summary>
|
||||
/// The future has a value ready.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// The future failed to resolve a value.
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the interface of an object that can be used to track a future value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object being retrieved.</typeparam>
|
||||
public interface IFuture<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state of the future.
|
||||
/// </summary>
|
||||
FutureState state { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value if the State is Success.
|
||||
/// </summary>
|
||||
T value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the failure exception if the State is Error.
|
||||
/// </summary>
|
||||
Exception error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke when an intermediate result is known.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnItem(FutureValueCallback<T> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnSuccess(FutureValueCallback<T> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnError(FutureErrorCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
IFuture<T> OnComplete(FutureCallback<T> callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the signature for callbacks used by the future.
|
||||
/// </summary>
|
||||
/// <param name="future">The future.</param>
|
||||
public delegate void FutureCallback<T>(IFuture<T> future);
|
||||
|
||||
public delegate void FutureValueCallback<T>(T value);
|
||||
public delegate void FutureErrorCallback(Exception error);
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IFuture{T}"/> that can be used internally by methods that return futures.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Methods should always return the <see cref="IFuture{T}"/> interface when calling code requests a future.
|
||||
/// This class is intended to be constructed internally in the method to provide a simple implementation of
|
||||
/// the interface. By returning the interface instead of the class it ensures the implementation can change
|
||||
/// later on if requirements change, without affecting the calling code.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of object being retrieved.</typeparam>
|
||||
public class Future<T> : IFuture<T>
|
||||
{
|
||||
private volatile FutureState _state;
|
||||
private T _value;
|
||||
private Exception _error;
|
||||
private Func<T> _processFunc;
|
||||
|
||||
private readonly List<FutureValueCallback<T>> _itemCallbacks = new List<FutureValueCallback<T>>();
|
||||
private readonly List<FutureValueCallback<T>> _successCallbacks = new List<FutureValueCallback<T>>();
|
||||
private readonly List<FutureErrorCallback> _errorCallbacks = new List<FutureErrorCallback>();
|
||||
private readonly List<FutureCallback<T>> _complationCallbacks = new List<FutureCallback<T>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the future.
|
||||
/// </summary>
|
||||
public FutureState state { get { return _state; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value if the State is Success.
|
||||
/// </summary>
|
||||
public T value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_state != FutureState.Success && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("value is not available unless state is Success or Processing.");
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the failure exception if the State is Error.
|
||||
/// </summary>
|
||||
public Exception error
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_state != FutureState.Error)
|
||||
{
|
||||
throw new InvalidOperationException("error is not available unless state is Error.");
|
||||
}
|
||||
|
||||
return _error;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Future{T}"/> class.
|
||||
/// </summary>
|
||||
public Future()
|
||||
{
|
||||
_state = FutureState.Pending;
|
||||
}
|
||||
|
||||
public IFuture<T> OnItem(FutureValueCallback<T> callback)
|
||||
{
|
||||
if (_state < FutureState.Success && !_itemCallbacks.Contains(callback))
|
||||
_itemCallbacks.Add(callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnSuccess(FutureValueCallback<T> callback)
|
||||
{
|
||||
if (_state == FutureState.Success)
|
||||
{
|
||||
callback(this.value);
|
||||
}
|
||||
else if (_state != FutureState.Error && !_successCallbacks.Contains(callback))
|
||||
{
|
||||
_successCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnError(FutureErrorCallback callback)
|
||||
{
|
||||
if (_state == FutureState.Error)
|
||||
{
|
||||
callback(this.error);
|
||||
}
|
||||
else if (_state != FutureState.Success && !_errorCallbacks.Contains(callback))
|
||||
{
|
||||
_errorCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke.</param>
|
||||
/// <returns>The future so additional calls can be chained together.</returns>
|
||||
public IFuture<T> OnComplete(FutureCallback<T> callback)
|
||||
{
|
||||
if (_state == FutureState.Success || _state == FutureState.Error)
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_complationCallbacks.Contains(callback))
|
||||
_complationCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
|
||||
/// <summary>
|
||||
/// Begins running a given function on a background thread to resolve the future's value, as long
|
||||
/// as it is still in the Pending state.
|
||||
/// </summary>
|
||||
/// <param name="func">The function that will retrieve the desired value.</param>
|
||||
public IFuture<T> Process(Func<T> func)
|
||||
{
|
||||
if (_state != FutureState.Pending)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot process a future that isn't in the Pending state.");
|
||||
}
|
||||
|
||||
BeginProcess();
|
||||
_processFunc = func;
|
||||
|
||||
#if NETFX_CORE
|
||||
#pragma warning disable 4014
|
||||
Windows.System.Threading.ThreadPool.RunAsync(ThreadFunc);
|
||||
#pragma warning restore 4014
|
||||
#else
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(ThreadFunc);
|
||||
#endif
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private
|
||||
#if NETFX_CORE
|
||||
async
|
||||
#endif
|
||||
void ThreadFunc(object param)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Directly call the Impl version to avoid the state validation of the public method
|
||||
AssignImpl(_processFunc());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Directly call the Impl version to avoid the state validation of the public method
|
||||
FailImpl(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_processFunc = null;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 1998
|
||||
|
||||
/// <summary>
|
||||
/// Allows manually assigning a value to a future, as long as it is still in the pending state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are times where you may not need to do background processing for a value. For example,
|
||||
/// you may have a cache of values and can just hand one out. In those cases you still want to
|
||||
/// return a future for the method signature, but can just call this method to fill in the future.
|
||||
/// </remarks>
|
||||
/// <param name="value">The value to assign the future.</param>
|
||||
public void Assign(T value)
|
||||
{
|
||||
if (_state != FutureState.Pending && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot assign a value to a future that isn't in the Pending or Processing state.");
|
||||
}
|
||||
|
||||
AssignImpl(value);
|
||||
}
|
||||
|
||||
public void BeginProcess(T initialItem = default(T))
|
||||
{
|
||||
_state = FutureState.Processing;
|
||||
_value = initialItem;
|
||||
}
|
||||
|
||||
public void AssignItem(T value)
|
||||
{
|
||||
_value = value;
|
||||
_error = null;
|
||||
|
||||
foreach (var callback in _itemCallbacks)
|
||||
callback(this.value);
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
_state = FutureState.Success;
|
||||
|
||||
FlushSuccessCallbacks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows manually failing a future, as long as it is still in the pending state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As with the Assign method, there are times where you may know a future value is a failure without
|
||||
/// doing any background work. In those cases you can simply fail the future manually and return it.
|
||||
/// </remarks>
|
||||
/// <param name="error">The exception to use to fail the future.</param>
|
||||
public void Fail(Exception error)
|
||||
{
|
||||
if (_state != FutureState.Pending && _state != FutureState.Processing)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot fail future that isn't in the Pending or Processing state.");
|
||||
}
|
||||
|
||||
FailImpl(error);
|
||||
}
|
||||
|
||||
private void AssignImpl(T value)
|
||||
{
|
||||
_value = value;
|
||||
_error = null;
|
||||
_state = FutureState.Success;
|
||||
|
||||
FlushSuccessCallbacks();
|
||||
}
|
||||
|
||||
private void FailImpl(Exception error)
|
||||
{
|
||||
_value = default(T);
|
||||
_error = error;
|
||||
_state = FutureState.Error;
|
||||
|
||||
FlushErrorCallbacks();
|
||||
}
|
||||
|
||||
private void FlushSuccessCallbacks()
|
||||
{
|
||||
foreach (var callback in _successCallbacks)
|
||||
callback(this.value);
|
||||
|
||||
FlushComplationCallbacks();
|
||||
}
|
||||
|
||||
private void FlushErrorCallbacks()
|
||||
{
|
||||
foreach (var callback in _errorCallbacks)
|
||||
callback(this.error);
|
||||
|
||||
FlushComplationCallbacks();
|
||||
}
|
||||
|
||||
private void FlushComplationCallbacks()
|
||||
{
|
||||
foreach (var callback in _complationCallbacks)
|
||||
callback(this);
|
||||
ClearCallbacks();
|
||||
}
|
||||
|
||||
private void ClearCallbacks()
|
||||
{
|
||||
_itemCallbacks.Clear();
|
||||
_successCallbacks.Clear();
|
||||
_errorCallbacks.Clear();
|
||||
_complationCallbacks.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9009e6f5e86bd5459d443846e42be9e
|
||||
timeCreated: 1515175777
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,267 @@
|
||||
#if CSHARP_7_OR_LATER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BestHTTP.Logger;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BestHTTP
|
||||
{
|
||||
public sealed class AsyncHTTPException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Status code of the server's response.
|
||||
/// </summary>
|
||||
public int StatusCode;
|
||||
|
||||
/// <summary>
|
||||
/// Content sent by the server.
|
||||
/// </summary>
|
||||
public string Content;
|
||||
|
||||
public AsyncHTTPException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncHTTPException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncHTTPException(int statusCode, string message, string content)
|
||||
:base(message)
|
||||
{
|
||||
this.StatusCode = statusCode;
|
||||
this.Content = content;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("StatusCode: {0}, Message: {1}, Content: {2}, StackTrace: {3}", this.StatusCode, this.Message, this.Content, this.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HTTPRequestAsyncExtensions
|
||||
{
|
||||
public static Task<HTTPResponse> GetHTTPResponseAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<HTTPResponse>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
tcs.TrySetResult(resp);
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<string> GetAsStringAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<string>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.DataAsText);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<Texture2D> GetAsTexture2DAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<Texture2D>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.DataAsTexture2D);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<byte[]> GetRawDataAsync(this HTTPRequest request, CancellationToken token = default)
|
||||
{
|
||||
return CreateTask<byte[]>(request, token, (req, resp, tcs) =>
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
tcs.TrySetResult(resp.Data);
|
||||
else
|
||||
tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
|
||||
|
||||
tcs.TrySetException(CreateException("No Exception", null, req.Exception));
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
VerboseLogging(request, "Request Aborted!");
|
||||
|
||||
tcs.TrySetCanceled();
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
VerboseLogging(request, "Connection Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Connection Timed Out!"));
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
VerboseLogging(request, "Processing the request Timed Out!");
|
||||
|
||||
tcs.TrySetException(CreateException("Processing the request Timed Out!"));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static Task<T> CreateTask<T>(HTTPRequest request, CancellationToken token, Action<HTTPRequest, HTTPResponse, TaskCompletionSource<T>> callback)
|
||||
{
|
||||
HTTPManager.Setup();
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
|
||||
request.Callback = (req, resp) =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
tcs.SetCanceled();
|
||||
else
|
||||
callback(req, resp, tcs);
|
||||
};
|
||||
|
||||
if (token.CanBeCanceled)
|
||||
token.Register((state) => (state as HTTPRequest)?.Abort(), request);
|
||||
|
||||
if (request.State == HTTPRequestStates.Initial)
|
||||
request.Send();
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static void VerboseLogging(HTTPRequest request, string str)
|
||||
{
|
||||
HTTPManager.Logger.Verbose("HTTPRequestAsyncExtensions", str, request.Context);
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static Exception CreateException(string errorMessage, HTTPResponse resp = null, Exception ex = null)
|
||||
{
|
||||
if (resp != null)
|
||||
return new AsyncHTTPException(resp.StatusCode, resp.Message, resp.DataAsText);
|
||||
else if (ex != null)
|
||||
return new AsyncHTTPException(ex.Message, ex);
|
||||
else
|
||||
return new AsyncHTTPException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 875eda53b9b14784484c59463d205081
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Will parse a comma-separeted header value
|
||||
/// </summary>
|
||||
public sealed class HeaderParser : KeyValuePairList
|
||||
{
|
||||
public HeaderParser(string headerStr)
|
||||
{
|
||||
base.Values = Parse(headerStr);
|
||||
}
|
||||
|
||||
private List<HeaderValue> Parse(string headerStr)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
int pos = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (pos < headerStr.Length)
|
||||
{
|
||||
HeaderValue current = new HeaderValue();
|
||||
|
||||
current.Parse(headerStr, ref pos);
|
||||
|
||||
result.Add(current);
|
||||
}
|
||||
}
|
||||
catch(System.Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("HeaderParser - Parse", headerStr, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40ea771765c2ae549abc4c264e500b76
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BestHTTP.PlatformSupport.Text;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in string parsers. Its Value is optional.
|
||||
/// </summary>
|
||||
public sealed class HeaderValue
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public List<HeaderValue> Options { get; set; }
|
||||
|
||||
public bool HasValue { get { return !string.IsNullOrEmpty(this.Value); } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public HeaderValue()
|
||||
{ }
|
||||
|
||||
public HeaderValue(string key)
|
||||
{
|
||||
this.Key = key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Helper Functions
|
||||
|
||||
public void Parse(string headerStr, ref int pos)
|
||||
{
|
||||
ParseImplementation(headerStr, ref pos, true);
|
||||
}
|
||||
|
||||
public bool TryGetOption(string key, out HeaderValue option)
|
||||
{
|
||||
option = null;
|
||||
|
||||
if (Options == null || Options.Count == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < Options.Count; ++i)
|
||||
if (String.Equals(Options[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
option = Options[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Functions
|
||||
|
||||
private void ParseImplementation(string headerStr, ref int pos, bool isOptionIsAnOption)
|
||||
{
|
||||
// According to https://tools.ietf.org/html/rfc7234#section-5.2.1.1
|
||||
// Max-Age has a form "max-age=5", but some (imgur.com specifically) sends it as "max-age:5"
|
||||
string key = headerStr.Read(ref pos, (ch) => ch != ';' && ch != '=' && ch != ':' && ch != ',', true);
|
||||
this.Key = key;
|
||||
|
||||
char? skippedChar = headerStr.Peek(pos - 1);
|
||||
bool isValue = skippedChar == '=' || skippedChar == ':';
|
||||
bool isOption = isOptionIsAnOption && skippedChar == ';';
|
||||
|
||||
while ((skippedChar != null && isValue || isOption) && pos < headerStr.Length)
|
||||
{
|
||||
|
||||
if (isValue)
|
||||
{
|
||||
string value = headerStr.ReadPossibleQuotedText(ref pos);
|
||||
this.Value = value;
|
||||
}
|
||||
else if (isOption)
|
||||
{
|
||||
HeaderValue option = new HeaderValue();
|
||||
option.ParseImplementation(headerStr, ref pos, false);
|
||||
|
||||
if (this.Options == null)
|
||||
this.Options = new List<HeaderValue>();
|
||||
|
||||
this.Options.Add(option);
|
||||
}
|
||||
|
||||
if (!isOptionIsAnOption)
|
||||
return;
|
||||
|
||||
skippedChar = headerStr.Peek(pos - 1);
|
||||
isValue = skippedChar == '=';
|
||||
isOption = isOptionIsAnOption && skippedChar == ';';
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Options != null && this.Options.Count > 0)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Get(4); //new StringBuilder(4);
|
||||
sb.Append(Key);
|
||||
sb.Append("=");
|
||||
sb.Append(Value);
|
||||
|
||||
foreach(var option in Options)
|
||||
{
|
||||
sb.Append(";");
|
||||
sb.Append(option.ToString());
|
||||
}
|
||||
|
||||
return StringBuilderPool.ReleaseAndGrab(sb);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Value))
|
||||
return Key + '=' + Value;
|
||||
else
|
||||
return Key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1465d4183951bf64dad0047b7c6e7243
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public interface IHeartbeat
|
||||
{
|
||||
void OnHeartbeatUpdate(TimeSpan dif);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A manager class that can handle subscribing and unsubscribeing in the same update.
|
||||
/// </summary>
|
||||
public sealed class HeartbeatManager
|
||||
{
|
||||
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private List<IHeartbeat> Heartbeats = new List<IHeartbeat>();
|
||||
private IHeartbeat[] UpdateArray;
|
||||
private DateTime LastUpdate = DateTime.MinValue;
|
||||
|
||||
public void Subscribe(IHeartbeat heartbeat)
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!Heartbeats.Contains(heartbeat))
|
||||
Heartbeats.Add(heartbeat);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(IHeartbeat heartbeat)
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Heartbeats.Remove(heartbeat);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (LastUpdate == DateTime.MinValue)
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
else
|
||||
{
|
||||
TimeSpan dif = DateTime.UtcNow - LastUpdate;
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
|
||||
int count = 0;
|
||||
|
||||
rwLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (UpdateArray == null || UpdateArray.Length < Heartbeats.Count)
|
||||
Array.Resize(ref UpdateArray, Heartbeats.Count);
|
||||
|
||||
Heartbeats.CopyTo(0, UpdateArray, 0, Heartbeats.Count);
|
||||
|
||||
count = Heartbeats.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitReadLock();
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateArray[i].OnHeartbeatUpdate(dif);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
rwLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Heartbeats.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
rwLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c610eeb580ec8e4c90789edb631cc76
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for specialized parsers
|
||||
/// </summary>
|
||||
public class KeyValuePairList
|
||||
{
|
||||
public List<HeaderValue> Values { get; protected set; }
|
||||
|
||||
public bool TryGet(string valueKeyName, out HeaderValue @param)
|
||||
{
|
||||
@param = null;
|
||||
for (int i = 0; i < Values.Count; ++i)
|
||||
if (string.CompareOrdinal(Values[i].Key, valueKeyName) == 0)
|
||||
{
|
||||
@param = Values[i];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 834cc34ef3418894589ef2e8389ff8c0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,32 @@
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public sealed class PeekableIncomingSegmentStream : BufferSegmentStream
|
||||
{
|
||||
private int peek_listIdx;
|
||||
private int peek_pos;
|
||||
|
||||
public void BeginPeek()
|
||||
{
|
||||
peek_listIdx = 0;
|
||||
peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
|
||||
}
|
||||
|
||||
public int PeekByte()
|
||||
{
|
||||
if (base.bufferList.Count == 0)
|
||||
return -1;
|
||||
|
||||
var segment = base.bufferList[this.peek_listIdx];
|
||||
if (peek_pos >= segment.Offset + segment.Count)
|
||||
{
|
||||
if (base.bufferList.Count <= this.peek_listIdx + 1)
|
||||
return -1;
|
||||
|
||||
segment = base.bufferList[++this.peek_listIdx];
|
||||
this.peek_pos = segment.Offset;
|
||||
}
|
||||
|
||||
return segment.Data[this.peek_pos++];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e917d28b8c5afd64fa0373d88a939bfe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,144 @@
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public sealed class ReadOnlyBufferedStream : Stream
|
||||
{
|
||||
Stream stream;
|
||||
public const int READBUFFER = 8192;
|
||||
byte[] buf;
|
||||
int available = 0;
|
||||
int pos = 0;
|
||||
|
||||
public ReadOnlyBufferedStream(Stream nstream)
|
||||
:this(nstream, READBUFFER)
|
||||
{
|
||||
}
|
||||
|
||||
public ReadOnlyBufferedStream(Stream nstream, int bufferSize)
|
||||
{
|
||||
stream = nstream;
|
||||
buf = BufferPool.Get(bufferSize, true);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int size)
|
||||
{
|
||||
if (available > 0)
|
||||
{
|
||||
// copy & return
|
||||
int copyCount = Math.Min(available, size);
|
||||
Array.Copy(buf, pos, buffer, offset, copyCount);
|
||||
pos += copyCount;
|
||||
available -= copyCount;
|
||||
return copyCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size >= buf.Length)
|
||||
{
|
||||
// read directly to buffer
|
||||
return stream.Read(buffer, offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// read to buf and copy
|
||||
pos = 0;
|
||||
available = stream.Read(buf, 0, buf.Length);
|
||||
|
||||
if (available > 0)
|
||||
return Read(buffer, offset, size);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (available > 0)
|
||||
{
|
||||
available -= 1;
|
||||
pos += 1;
|
||||
return buf[pos - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
available = stream.Read(buf, 0, buf.Length);
|
||||
pos = 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (available < 1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
available -= 1;
|
||||
pos += 1;
|
||||
return buf[pos - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && buf != null)
|
||||
BufferPool.Release(buf);
|
||||
|
||||
buf = null;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6b9a4ff6813b4f4b9f2ffa453c3fccc
|
||||
timeCreated: 1543513046
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,152 @@
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper of multiple streams. Writes and reads are both supported. Read goes trough all the streams.
|
||||
/// </summary>
|
||||
public sealed class StreamList : System.IO.Stream
|
||||
{
|
||||
private System.IO.Stream[] Streams;
|
||||
private int CurrentIdx;
|
||||
|
||||
public StreamList(params System.IO.Stream[] streams)
|
||||
{
|
||||
this.Streams = streams;
|
||||
this.CurrentIdx = 0;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return false;
|
||||
return Streams[CurrentIdx].CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanSeek { get { return false; } }
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return false;
|
||||
return Streams[CurrentIdx].CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return;
|
||||
|
||||
// We have to call the flush to all previous streams, as we may advanced the CurrentIdx
|
||||
for (int i = 0; i <= CurrentIdx; ++i)
|
||||
Streams[i].Flush();
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return 0;
|
||||
|
||||
long length = 0;
|
||||
for (int i = 0; i < Streams.Length; ++i)
|
||||
length += Streams[i].Length;
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return -1;
|
||||
|
||||
int readCount = Streams[CurrentIdx].Read(buffer, offset, count);
|
||||
|
||||
while (readCount < count && ++CurrentIdx < Streams.Length)
|
||||
{
|
||||
// Dispose previous stream
|
||||
try
|
||||
{
|
||||
Streams[CurrentIdx - 1].Dispose();
|
||||
Streams[CurrentIdx - 1] = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("StreamList", "Dispose", ex);
|
||||
}
|
||||
|
||||
readCount += Streams[CurrentIdx].Read(buffer, offset + readCount, count - readCount);
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return;
|
||||
|
||||
Streams[CurrentIdx].Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public void Write(string str)
|
||||
{
|
||||
var buffer = str.GetASCIIBytes();
|
||||
this.Write(buffer.Data, buffer.Offset, buffer.Count);
|
||||
BufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (int i = 0; i < Streams.Length; ++i)
|
||||
if (Streams[i] != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Streams[i].Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("StreamList", "Dispose", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException("Position get");
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException("Position set");
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, System.IO.SeekOrigin origin)
|
||||
{
|
||||
if (CurrentIdx >= Streams.Length)
|
||||
return 0;
|
||||
|
||||
return Streams[CurrentIdx].Seek(offset, origin);
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException("SetLength");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9a35feccd354604bb0037b7e66a3e22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
77
JNFrame2/Assets/Plugins/Best HTTP/Source/Extensions/Timer.cs
Normal file
77
JNFrame2/Assets/Plugins/Best HTTP/Source/Extensions/Timer.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
public
|
||||
#if CSHARP_7_OR_LATER
|
||||
readonly
|
||||
#endif
|
||||
struct TimerData
|
||||
{
|
||||
public readonly DateTime Created;
|
||||
public readonly TimeSpan Interval;
|
||||
public readonly object Context;
|
||||
|
||||
public readonly Func<DateTime, object, bool> OnTimer;
|
||||
|
||||
public bool IsOnTime(DateTime now)
|
||||
{
|
||||
return now >= this.Created + this.Interval;
|
||||
}
|
||||
|
||||
public TimerData(TimeSpan interval, object context, Func<DateTime, object, bool> onTimer)
|
||||
{
|
||||
this.Created = DateTime.Now;
|
||||
this.Interval = interval;
|
||||
this.Context = context;
|
||||
this.OnTimer = onTimer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new TimerData but the Created field will be set to the current time.
|
||||
/// </summary>
|
||||
public TimerData CreateNew()
|
||||
{
|
||||
return new TimerData(this.Interval, this.Context, this.OnTimer);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[TimerData Created: {0}, Interval: {1}, IsOnTime: {2}]", this.Created.ToString(System.Globalization.CultureInfo.InvariantCulture), this.Interval, this.IsOnTime(DateTime.Now));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Timer
|
||||
{
|
||||
private static List<TimerData> Timers = new List<TimerData>();
|
||||
|
||||
public static void Add(TimerData timer)
|
||||
{
|
||||
Timers.Add(timer);
|
||||
}
|
||||
|
||||
internal static void Process()
|
||||
{
|
||||
if (Timers.Count == 0)
|
||||
return;
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
for (int i = 0; i < Timers.Count; ++i)
|
||||
{
|
||||
TimerData timer = Timers[i];
|
||||
|
||||
if (timer.IsOnTime(now))
|
||||
{
|
||||
bool repeat = timer.OnTimer(now, timer.Context);
|
||||
|
||||
if (repeat)
|
||||
Timers[i] = timer.CreateNew();
|
||||
else
|
||||
Timers.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b79070540cbf6f42b89c74c2fb978f5
|
||||
timeCreated: 1571210041
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for parsing WWW-Authenticate headers:
|
||||
/// Digest realm="my realm", nonce="4664b327a2963503ba58bbe13ad672c0", qop=auth, opaque="f7e38bdc1c66fce214f9019ffe43117c"
|
||||
/// </summary>
|
||||
public sealed class WWWAuthenticateHeaderParser : KeyValuePairList
|
||||
{
|
||||
public WWWAuthenticateHeaderParser(string headerValue)
|
||||
{
|
||||
Values = ParseQuotedHeader(headerValue);
|
||||
}
|
||||
|
||||
private List<HeaderValue> ParseQuotedHeader(string str)
|
||||
{
|
||||
List<HeaderValue> result = new List<HeaderValue>();
|
||||
|
||||
if (str != null)
|
||||
{
|
||||
|
||||
int idx = 0;
|
||||
|
||||
// Read Type (Basic|Digest)
|
||||
string type = str.Read(ref idx, (ch) => !char.IsWhiteSpace(ch) && !char.IsControl(ch)).TrimAndLower();
|
||||
result.Add(new HeaderValue(type));
|
||||
|
||||
// process the rest of the text
|
||||
while (idx < str.Length)
|
||||
{
|
||||
// Read key
|
||||
string key = str.Read(ref idx, '=').TrimAndLower();
|
||||
HeaderValue qp = new HeaderValue(key);
|
||||
|
||||
// Skip any white space
|
||||
str.SkipWhiteSpace(ref idx);
|
||||
|
||||
qp.Value = str.ReadPossibleQuotedText(ref idx);
|
||||
|
||||
result.Add(qp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cba91161fe643c419eedafefdf1f959
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,82 @@
|
||||
using BestHTTP.PlatformSupport.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom buffer stream implementation that will not close the underlying stream.
|
||||
/// </summary>
|
||||
public sealed class WriteOnlyBufferedStream : Stream
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
public override long Length { get { return this.buffer.Length; } }
|
||||
|
||||
public override long Position { get { return this._position; } set { throw new NotImplementedException("Position set"); } }
|
||||
private int _position;
|
||||
|
||||
private byte[] buffer;
|
||||
private Stream stream;
|
||||
|
||||
public WriteOnlyBufferedStream(Stream stream, int bufferSize)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
this.buffer = BufferPool.Get(bufferSize, true);
|
||||
this._position = 0;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (this._position > 0)
|
||||
{
|
||||
this.stream.Write(this.buffer, 0, this._position);
|
||||
this.stream.Flush();
|
||||
|
||||
//if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||||
// HTTPManager.Logger.Information("WriteOnlyBufferedStream", string.Format("Flushed {0:N0} bytes", this._position));
|
||||
|
||||
this._position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(byte[] bufferFrom, int offset, int count)
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
int writeCount = Math.Min(count, this.buffer.Length - this._position);
|
||||
Array.Copy(bufferFrom, offset, this.buffer, this._position, writeCount);
|
||||
|
||||
this._position += writeCount;
|
||||
offset += writeCount;
|
||||
count -= writeCount;
|
||||
|
||||
if (this._position == this.buffer.Length)
|
||||
this.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void SetLength(long value) { }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing && this.buffer != null)
|
||||
BufferPool.Release(this.buffer);
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2df120a767773ee478acf872b86ae326
|
||||
timeCreated: 1530027551
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user