2019-08-23 11:56:54 -07:00
|
|
|
using System;
|
|
|
|
using System.IO;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Diagnostics;
|
2020-04-29 06:40:03 -07:00
|
|
|
using System.Collections;
|
2019-08-23 11:56:54 -07:00
|
|
|
|
2020-07-28 05:57:45 -07:00
|
|
|
namespace CURL
|
2019-08-23 11:56:54 -07:00
|
|
|
{
|
|
|
|
class Transfer
|
|
|
|
{
|
|
|
|
CURL.Easy mCurl = new CURL.Easy() ~ delete _;
|
|
|
|
bool mCancelling = false;
|
|
|
|
List<uint8> mData = new List<uint8>() ~ delete _;
|
|
|
|
Stopwatch mStatsTimer = new Stopwatch() ~ delete _;
|
|
|
|
WaitEvent mDoneEvent ~ delete _;
|
|
|
|
Result<Span<uint8>> mResult;
|
|
|
|
|
|
|
|
int mTotalBytes = -1;
|
|
|
|
int mBytesReceived = 0;
|
|
|
|
|
|
|
|
int mLastBytesPerSecond = -1;
|
|
|
|
int mBytesAtLastPeriod = 0;
|
|
|
|
bool mRunning;
|
|
|
|
|
|
|
|
public int BytesPerSecond
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
UpdateBytesPerSecond();
|
|
|
|
if (mLastBytesPerSecond != -1)
|
|
|
|
return mLastBytesPerSecond;
|
|
|
|
return GetCurBytesPerSecond();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public int TotalBytes
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
return mTotalBytes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public int BytesReceived
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
return mBytesReceived;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsRunning
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
return mRunning;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public this()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public ~this()
|
|
|
|
{
|
|
|
|
mCancelling = true;
|
|
|
|
if (mRunning)
|
|
|
|
mDoneEvent.WaitFor();
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetCurBytesPerSecond()
|
|
|
|
{
|
|
|
|
int elapsedTime = mStatsTimer.ElapsedMilliseconds;
|
|
|
|
if (elapsedTime == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return (int)((int64)(mBytesReceived - mBytesAtLastPeriod) * 1000 / elapsedTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateBytesPerSecond()
|
|
|
|
{
|
|
|
|
if (mStatsTimer.ElapsedMilliseconds >= 500)
|
|
|
|
{
|
|
|
|
mLastBytesPerSecond = GetCurBytesPerSecond();
|
|
|
|
mBytesAtLastPeriod = mBytesReceived;
|
|
|
|
mStatsTimer.Restart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Update(int dltotal, int dlnow)
|
|
|
|
{
|
|
|
|
if (dltotal > mTotalBytes)
|
|
|
|
mTotalBytes = dltotal;
|
|
|
|
mBytesReceived = dlnow;
|
|
|
|
UpdateBytesPerSecond();
|
|
|
|
}
|
|
|
|
|
2020-05-25 20:46:28 +08:00
|
|
|
[CallingConvention(.Stdcall)]
|
2019-08-23 11:56:54 -07:00
|
|
|
static int Callback(void *p, int dltotal, int dlnow, int ultotal, int ulnow)
|
|
|
|
{
|
|
|
|
Transfer transfer = (Transfer)Internal.UnsafeCastToObject(p);
|
|
|
|
if (transfer.mCancelling)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
transfer.Update(dltotal, dlnow);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-05-25 20:46:28 +08:00
|
|
|
[CallingConvention(.Stdcall)]
|
2019-08-23 11:56:54 -07:00
|
|
|
static int Write(void* dataPtr, int size, int count, void* ctx)
|
|
|
|
{
|
|
|
|
Transfer transfer = (Transfer)Internal.UnsafeCastToObject(ctx);
|
|
|
|
int byteCount = size * count;
|
|
|
|
if (byteCount > 0)
|
|
|
|
{
|
|
|
|
Internal.MemCpy(transfer.mData.GrowUnitialized(byteCount), dataPtr, byteCount);
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2020-07-28 05:57:45 -07:00
|
|
|
public void Init(String url)
|
2019-08-23 11:56:54 -07:00
|
|
|
{
|
|
|
|
function int(void *p, int dltotal, int dlnow, int ultotal, int ulnow) callback = => Callback;
|
|
|
|
mCurl.SetOptFunc(.XferInfoFunction, (void*)callback);
|
|
|
|
mCurl.SetOpt(.XferInfoData, Internal.UnsafeCastToPtr(this));
|
|
|
|
|
|
|
|
function int(void* ptr, int size, int count, void* ctx) writeFunc = => Write;
|
|
|
|
mCurl.SetOptFunc(.WriteFunction, (void*)writeFunc);
|
|
|
|
mCurl.SetOpt(.WriteData, Internal.UnsafeCastToPtr(this));
|
|
|
|
|
|
|
|
mCurl.SetOpt(.FollowLocation, true);
|
|
|
|
mCurl.SetOpt(.URL, url);
|
|
|
|
mCurl.SetOpt(.NoProgress, false);
|
2020-11-21 07:10:09 -08:00
|
|
|
mCurl.SetOpt(.IPResolve, (int)CURL.Easy.IPResolve.V4);
|
2019-08-23 11:56:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public Result<Span<uint8>> Perform()
|
|
|
|
{
|
|
|
|
mStatsTimer.Start();
|
|
|
|
var result = mCurl.Perform();
|
|
|
|
mStatsTimer.Stop();
|
|
|
|
|
|
|
|
switch (result)
|
|
|
|
{
|
|
|
|
case .Err:
|
|
|
|
return .Err;
|
|
|
|
default:
|
|
|
|
if (mData.Count > 0)
|
|
|
|
return .Ok(.(&mData[0], mData.Count));
|
|
|
|
else
|
|
|
|
return .Ok(.());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DoBackground()
|
|
|
|
{
|
|
|
|
mResult = Perform();
|
|
|
|
mRunning = false;
|
|
|
|
mDoneEvent.Set(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Result<void> PerformBackground()
|
|
|
|
{
|
|
|
|
// This is a one-use object
|
|
|
|
if (mDoneEvent != null)
|
|
|
|
return .Err;
|
|
|
|
mDoneEvent = new WaitEvent();
|
|
|
|
mRunning = true;
|
|
|
|
ThreadPool.QueueUserWorkItem(new => DoBackground);
|
|
|
|
return .Ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Result<Span<uint8>> GetResult()
|
|
|
|
{
|
|
|
|
if (mDoneEvent != null)
|
|
|
|
mDoneEvent.WaitFor();
|
|
|
|
return mResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Cancel(bool wait = false)
|
|
|
|
{
|
|
|
|
mCancelling = true;
|
|
|
|
if ((wait) && (mRunning))
|
|
|
|
mDoneEvent.WaitFor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|