mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-07 19:18:19 +02:00
652 lines
No EOL
14 KiB
C++
652 lines
No EOL
14 KiB
C++
#include "NetManager.h"
|
|
#include "DebugManager.h"
|
|
#include "Compiler/BfSystem.h"
|
|
|
|
#include "BeefySysLib/util/AllocDebug.h"
|
|
|
|
USING_NS_BF;
|
|
|
|
#ifdef BF_CURL
|
|
#define CURL_STATICLIB
|
|
#include "curl/curl.h"
|
|
#include "curl/multi.h"
|
|
|
|
static int TransferInfoCallback(void* userp,
|
|
curl_off_t dltotal, curl_off_t dlnow,
|
|
curl_off_t ultotal, curl_off_t ulnow)
|
|
{
|
|
NetRequest* netRequest = (NetRequest*)userp;
|
|
if (netRequest->mCancelling)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp)
|
|
{
|
|
NetRequest* netRequest = (NetRequest*)userp;
|
|
|
|
long response_code = 0;
|
|
curl_easy_getinfo(netRequest->mCURL, CURLINFO_RESPONSE_CODE, &response_code);
|
|
|
|
if (netRequest->mCancelling)
|
|
{
|
|
netRequest->mFailed = true;
|
|
return 0;
|
|
}
|
|
|
|
if (response_code != 200)
|
|
return 0;
|
|
|
|
if (netRequest->mFailed)
|
|
return 0;
|
|
|
|
if (!netRequest->mOutFile.IsOpen())
|
|
{
|
|
RecursiveCreateDirectory(GetFileDir(netRequest->mOutPath));
|
|
if (!netRequest->mOutFile.Open(netRequest->mOutTempPath, BfpFileCreateKind_CreateAlways, BfpFileCreateFlag_Write))
|
|
{
|
|
netRequest->Fail(StrFormat("Failed to create file '%s'", netRequest->mOutTempPath.c_str()));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32 tickNow = BFTickCount();
|
|
|
|
if (tickNow - netRequest->mLastUpdateTick >= 500)
|
|
{
|
|
curl_off_t downloadSize = 0;
|
|
curl_easy_getinfo(netRequest->mCURL, CURLINFO_SIZE_DOWNLOAD_T, &downloadSize);
|
|
curl_off_t length = 0;
|
|
curl_easy_getinfo(netRequest->mCURL, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length);
|
|
|
|
if (netRequest->mShowTracking)
|
|
{
|
|
String msg = StrFormat("symsrv Getting '%s' - %dk", netRequest->mURL.c_str(), (int)(downloadSize / 1024));
|
|
|
|
if (length > 0)
|
|
{
|
|
msg += StrFormat(" (%d%%)", (int)(downloadSize * 100 / length));
|
|
}
|
|
|
|
netRequest->mNetManager->mDebugManager->OutputRawMessage(msg);
|
|
}
|
|
|
|
netRequest->mLastUpdateTick = tickNow;
|
|
}
|
|
|
|
size_t realsize = size * nmemb;
|
|
netRequest->mOutFile.Write(contents, (int)realsize);
|
|
return realsize;
|
|
}
|
|
|
|
void NetRequest::Cleanup()
|
|
{
|
|
if (mCURLMulti != NULL)
|
|
curl_multi_remove_handle(mCURLMulti, mCURL);
|
|
if (mCURL != NULL)
|
|
curl_easy_cleanup(mCURL);
|
|
if (mCURLMulti != NULL)
|
|
curl_multi_cleanup(mCURLMulti);
|
|
|
|
mCURL = NULL;
|
|
mCURLMulti = NULL;
|
|
}
|
|
|
|
void NetRequest::DoTransfer()
|
|
{
|
|
if (mCancelling)
|
|
return;
|
|
|
|
// {
|
|
// mFailed = true;
|
|
// return;
|
|
// }
|
|
|
|
long response_code = 0;
|
|
for (int pass = 0; pass < 3; pass++)
|
|
{
|
|
BfLogDbg("NetManager starting get on %s Pass:%d\n", mURL.c_str(), pass);
|
|
mNetManager->mDebugManager->OutputRawMessage(StrFormat("msgLo Getting '%s'\n", mURL.c_str()));
|
|
|
|
mOutTempPath = mOutPath + "__partial";
|
|
|
|
mCURLMulti = curl_multi_init();
|
|
|
|
mCURL = curl_easy_init();
|
|
|
|
if (mShowTracking)
|
|
{
|
|
mNetManager->mDebugManager->OutputRawMessage(StrFormat("symsrv Getting '%s'", mURL.c_str()));
|
|
}
|
|
|
|
//OutputDebugStrF("Getting '%s'\n", mURL.c_str());
|
|
|
|
curl_easy_setopt(mCURL, CURLOPT_URL, mURL.c_str());
|
|
curl_easy_setopt(mCURL, CURLOPT_WRITEDATA, (void*)this);
|
|
curl_easy_setopt(mCURL, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
|
curl_easy_setopt(mCURL, CURLOPT_XFERINFODATA, (void*)this);
|
|
curl_easy_setopt(mCURL, CURLOPT_XFERINFOFUNCTION, TransferInfoCallback);
|
|
curl_easy_setopt(mCURL, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(mCURL, CURLOPT_NOPROGRESS, 0L);
|
|
curl_easy_setopt(mCURL, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // Connects go slow without this
|
|
//auto result = curl_easy_perform(mCURL);
|
|
|
|
CURLMcode mcode = curl_multi_add_handle(mCURLMulti, mCURL);
|
|
if (mcode != CURLM_OK)
|
|
{
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
int activeCount = 0;
|
|
curl_multi_perform(mCURLMulti, &activeCount);
|
|
if (activeCount == 0)
|
|
break;
|
|
|
|
int waitRet = 0;
|
|
curl_multi_wait(mCURLMulti, NULL, 0, 20, &waitRet);
|
|
|
|
if (mCancelling)
|
|
{
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if (result != CURLE_OK)
|
|
// {
|
|
// mFailed = true;
|
|
// return;
|
|
// }
|
|
|
|
response_code = 0;
|
|
curl_easy_getinfo(mCURL, CURLINFO_RESPONSE_CODE, &response_code);
|
|
mNetManager->mDebugManager->OutputRawMessage(StrFormat("msgLo Result for '%s': %d\n", mURL.c_str(), response_code));
|
|
|
|
if (response_code == 0)
|
|
{
|
|
mError.Clear();
|
|
|
|
int msgs_left = 0;
|
|
CURLMsg* msg = curl_multi_info_read(mCURLMulti, &msgs_left);
|
|
|
|
if (msg != NULL && msg->msg == CURLMSG_DONE)
|
|
{
|
|
CURLcode return_code = msg->data.result;
|
|
mError.Append(curl_easy_strerror(return_code));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ((response_code == 200) || (response_code == 404))
|
|
break;
|
|
|
|
Cleanup();
|
|
// Try again!
|
|
}
|
|
|
|
if (response_code == 200)
|
|
{
|
|
curl_off_t downloadSize = 0;
|
|
curl_easy_getinfo(mCURL, CURLINFO_SIZE_DOWNLOAD_T, &downloadSize);
|
|
curl_off_t length = 0;
|
|
curl_easy_getinfo(mCURL, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length);
|
|
if ((downloadSize != 0) && (length != 0) && (downloadSize != length))
|
|
response_code = -1; // Partial download
|
|
}
|
|
|
|
if (response_code != 200)
|
|
{
|
|
mOutFile.Close();
|
|
// Bad result
|
|
mFailed = true;
|
|
return;
|
|
}
|
|
|
|
BfLogDbg("NetManager successfully completed %s\n", mURL.c_str());
|
|
|
|
if (mCancelOnSuccess != NULL)
|
|
mNetManager->Cancel(mCancelOnSuccess);
|
|
|
|
if (!mOutFile.IsOpen())
|
|
{
|
|
mFailed = true;
|
|
return; // No data
|
|
}
|
|
mOutFile.Close();
|
|
|
|
BfpFile_Delete(mOutPath.c_str(), NULL);
|
|
BfpFileResult renameResult;
|
|
BfpFile_Rename(mOutTempPath.c_str(), mOutPath.c_str(), &renameResult);
|
|
|
|
if (renameResult != BfpFileResult_Ok)
|
|
{
|
|
mFailed = true;
|
|
}
|
|
}
|
|
|
|
void NetRequest::Perform()
|
|
{
|
|
DoTransfer();
|
|
}
|
|
|
|
#elif defined BF_PLATFORM_WINDOWS
|
|
|
|
#include <windows.h>
|
|
#include <wininet.h>
|
|
#include <stdio.h>
|
|
|
|
#pragma comment (lib, "wininet.lib")
|
|
|
|
void NetRequest::Perform()
|
|
{
|
|
if (mCancelling)
|
|
return;
|
|
|
|
// {
|
|
// mFailed = true;
|
|
// return;
|
|
// }
|
|
|
|
BfLogDbg("NetManager starting get on %s\n", mURL.c_str());
|
|
|
|
String protoName;
|
|
String serverName;
|
|
String objectName;
|
|
|
|
int colonPos = (int)mURL.IndexOf("://");
|
|
if (colonPos == -1)
|
|
{
|
|
Fail("Invalid URL");
|
|
return;
|
|
}
|
|
|
|
protoName = mURL.Substring(0, colonPos);
|
|
serverName = mURL.Substring(colonPos + 3);
|
|
|
|
int slashPos = (int)serverName.IndexOf('/');
|
|
if (slashPos != -1)
|
|
{
|
|
objectName = serverName.Substring(slashPos);
|
|
serverName.RemoveToEnd(slashPos);
|
|
}
|
|
|
|
mOutTempPath = mOutPath + "__partial";
|
|
|
|
HINTERNET hSession = NULL;
|
|
HINTERNET hConnect = NULL;
|
|
HINTERNET hHttpFile = NULL;
|
|
|
|
defer
|
|
{
|
|
if (hHttpFile != NULL)
|
|
InternetCloseHandle(hHttpFile);
|
|
if (hConnect != NULL)
|
|
InternetCloseHandle(hConnect);
|
|
if (hSession != NULL)
|
|
InternetCloseHandle(hSession);
|
|
|
|
if (mOutFile.IsOpen())
|
|
mOutFile.Close();
|
|
};
|
|
|
|
bool isHttp = protoName.Equals("http", StringImpl::CompareKind_OrdinalIgnoreCase);
|
|
bool isHttps = protoName.Equals("https", StringImpl::CompareKind_OrdinalIgnoreCase);
|
|
if ((!isHttp) && (!isHttps))
|
|
{
|
|
Fail("Invalid URL protocol");
|
|
return;
|
|
}
|
|
|
|
hSession = InternetOpenW(
|
|
L"Mozilla/5.0",
|
|
INTERNET_OPEN_TYPE_PRECONFIG,
|
|
NULL,
|
|
NULL,
|
|
0);
|
|
|
|
hConnect = InternetConnectW(
|
|
hSession,
|
|
UTF8Decode(serverName).c_str(),
|
|
isHttps ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT,
|
|
NULL,
|
|
NULL,
|
|
INTERNET_SERVICE_HTTP,
|
|
0,
|
|
0);
|
|
|
|
DWORD httpOpenFlags = INTERNET_FLAG_RELOAD;
|
|
if (isHttps)
|
|
httpOpenFlags |= INTERNET_FLAG_SECURE;
|
|
hHttpFile = HttpOpenRequestW(
|
|
hConnect,
|
|
L"GET",
|
|
UTF8Decode(objectName).c_str(),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
httpOpenFlags,
|
|
0);
|
|
|
|
if (!HttpSendRequestW(hHttpFile, NULL, 0, 0, 0))
|
|
{
|
|
int err = GetLastError();
|
|
OutputDebugStrF("Failed: %s %X\n", mURL.c_str(), err);;
|
|
Fail("Failed to send request");
|
|
return;
|
|
}
|
|
|
|
DWORD statusCode = 0;
|
|
DWORD length = sizeof(DWORD);
|
|
HttpQueryInfo(hHttpFile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &length, NULL);
|
|
|
|
if (statusCode != 200)
|
|
{
|
|
Fail("Invalid HTTP response");
|
|
return;
|
|
}
|
|
|
|
if (mShowTracking)
|
|
{
|
|
mNetManager->mDebugManager->OutputRawMessage(StrFormat("symsrv Getting '%s'", mURL.c_str()));
|
|
}
|
|
|
|
uint8 buffer[4096];
|
|
while (true)
|
|
{
|
|
DWORD dwBytesRead = 0;
|
|
BOOL bRead = InternetReadFile(hHttpFile, buffer, 4096, &dwBytesRead);
|
|
if (dwBytesRead == 0)
|
|
break;
|
|
if (!bRead)
|
|
{
|
|
//printf("InternetReadFile error : <%lu>\n", GetLastError());
|
|
Fail("Failed to receive");
|
|
return;
|
|
}
|
|
|
|
if (!mOutFile.IsOpen())
|
|
{
|
|
RecursiveCreateDirectory(GetFileDir(mOutPath));
|
|
if (!mOutFile.Open(mOutTempPath, "wb"))
|
|
{
|
|
Fail(StrFormat("Failed to create file '%s'", mOutTempPath.c_str()));
|
|
return;
|
|
}
|
|
}
|
|
mOutFile.Write(buffer, (int)dwBytesRead);
|
|
}
|
|
|
|
BfLogDbg("NetManager successfully completed %s\n", mURL.c_str());
|
|
|
|
if (mCancelOnSuccess != NULL)
|
|
mNetManager->Cancel(mCancelOnSuccess);
|
|
|
|
if (!mOutFile.IsOpen())
|
|
{
|
|
mFailed = true;
|
|
return; // No data
|
|
}
|
|
mOutFile.Close();
|
|
|
|
BfpFile_Delete(mOutPath.c_str(), NULL);
|
|
BfpFileResult renameResult;
|
|
BfpFile_Rename(mOutTempPath.c_str(), mOutPath.c_str(), &renameResult);
|
|
|
|
if (renameResult != BfpFileResult_Ok)
|
|
mFailed = true;
|
|
}
|
|
|
|
void NetRequest::Cleanup()
|
|
{
|
|
}
|
|
|
|
#else
|
|
|
|
void NetRequest::Perform()
|
|
{
|
|
mFailed = true;
|
|
}
|
|
|
|
void NetRequest::Cleanup()
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
NetRequest::~NetRequest()
|
|
{
|
|
Cleanup();
|
|
// This is synchronized
|
|
if (mResult != NULL)
|
|
{
|
|
mResult->mFailed = mFailed;
|
|
mResult->mError = mError;
|
|
mResult->mCurRequest = NULL;
|
|
if (mResult->mDoneEvent != NULL)
|
|
{
|
|
mResult->mDoneEvent->Set(true);
|
|
BF_ASSERT(!mResult->mRemoved);
|
|
}
|
|
if (mResult->mRemoved)
|
|
delete mResult;
|
|
}
|
|
|
|
mNetManager->mRequestDoneEvent.Set();
|
|
}
|
|
|
|
void NetRequest::Fail(const StringImpl& error)
|
|
{
|
|
if (mFailed)
|
|
return;
|
|
|
|
mError = error;
|
|
mFailed = true;
|
|
}
|
|
|
|
bool NetRequest::Cancel()
|
|
{
|
|
mCancelling = true;
|
|
return true;
|
|
}
|
|
|
|
void NetRequest::ShowTracking()
|
|
{
|
|
//mNetManager->mDebugManager->OutputMessage(StrFormat("Getting '%s'\n", mURL.c_str()));
|
|
mNetManager->mDebugManager->OutputRawMessage(StrFormat("symsrv Getting '%s'", mURL.c_str()));
|
|
mShowTracking = true;
|
|
}
|
|
|
|
void NetManagerThread()
|
|
{
|
|
}
|
|
|
|
NetManager::NetManager() : mThreadPool(8, 1*1024*1024)
|
|
{
|
|
mWaitingResult = NULL;
|
|
mWaitingRequest = NULL;
|
|
}
|
|
|
|
NetManager::~NetManager()
|
|
{
|
|
mThreadPool.Shutdown();
|
|
|
|
for (auto kv : mCachedResults)
|
|
{
|
|
delete kv.mValue;
|
|
}
|
|
}
|
|
|
|
NetRequest* NetManager::CreateGetRequest(const StringImpl& url, const StringImpl& destPath, bool useCache)
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
|
|
NetRequest* netRequest = new NetRequest();
|
|
netRequest->mNetManager = this;
|
|
netRequest->mURL = url;
|
|
netRequest->mOutPath = destPath;
|
|
|
|
NetResult* netResult = new NetResult();
|
|
netResult->mURL = url;
|
|
netResult->mOutPath = destPath;
|
|
netResult->mFailed = false;
|
|
netResult->mCurRequest = netRequest;
|
|
|
|
if (useCache)
|
|
{
|
|
NetResult** netResultPtr;
|
|
if (mCachedResults.TryAdd(url, NULL, &netResultPtr))
|
|
{
|
|
*netResultPtr = netResult;
|
|
}
|
|
else
|
|
{
|
|
mOldResults.Add(*netResultPtr);
|
|
*netResultPtr = netResult;
|
|
}
|
|
}
|
|
|
|
netRequest->mResult = netResult;
|
|
|
|
return netRequest;
|
|
}
|
|
|
|
NetResult* NetManager::QueueGet(const StringImpl& url, const StringImpl& destPath, bool useCache)
|
|
{
|
|
BfLogDbg("NetManager queueing %s %d\n", url.c_str(), useCache);
|
|
|
|
auto netRequest = CreateGetRequest(url, destPath, useCache);
|
|
auto netResult = netRequest->mResult;
|
|
mThreadPool.AddJob(netRequest);
|
|
return netResult;
|
|
}
|
|
|
|
bool NetManager::Get(const StringImpl& url, const StringImpl& destPath)
|
|
{
|
|
NetRequest* netRequest = NULL;
|
|
int waitCount = 0;
|
|
while (true)
|
|
{
|
|
// Check cached
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
|
|
mWaitingResult = NULL;
|
|
NetResult* netResult;
|
|
if (mCachedResults.TryGetValue(url, &netResult))
|
|
{
|
|
if (netResult->mCurRequest == NULL)
|
|
{
|
|
BfLogDbg("NetManager::Get using cached result for %s: %d. WaitCount: %d \n", url.c_str(), !netResult->mFailed, waitCount);
|
|
return (!netResult->mFailed) && (FileExists(netResult->mOutPath));
|
|
}
|
|
else if (!netResult->mCurRequest->mShowTracking) // Is done?
|
|
{
|
|
if (!netResult->mCurRequest->mProcessing)
|
|
{
|
|
BfLogDbg("NetManager::Get pulling queued request into current thread %s\n", url.c_str());
|
|
netRequest = netResult->mCurRequest;
|
|
bool didRemove = mThreadPool.mJobs.Remove(netRequest);
|
|
BF_ASSERT(didRemove);
|
|
break;
|
|
}
|
|
|
|
mWaitingResult = netResult;
|
|
netResult->mCurRequest->ShowTracking();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
netRequest = CreateGetRequest(url, destPath, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
waitCount++;
|
|
mRequestDoneEvent.WaitFor();
|
|
}
|
|
|
|
// Perform this in the requesting thread
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
mWaitingRequest = netRequest;
|
|
}
|
|
|
|
netRequest->mShowTracking = true;
|
|
netRequest->Perform();
|
|
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
mWaitingRequest = NULL;
|
|
auto netResult = netRequest->mResult;
|
|
delete netRequest;
|
|
|
|
BfLogDbg("NetManager::Get requested %s: %d\n", url.c_str(), !netResult->mFailed);
|
|
mDebugManager->OutputRawMessage(StrFormat("msgLo Result for '%s': %d\n", url.c_str(), !netResult->mFailed));
|
|
|
|
return (!netResult->mFailed) && (FileExists(netResult->mOutPath));
|
|
}
|
|
|
|
void NetManager::CancelAll()
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
if (mWaitingRequest != NULL)
|
|
mWaitingRequest->Cancel();
|
|
mThreadPool.CancelAll();
|
|
}
|
|
|
|
void NetManager::Clear()
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
BF_ASSERT(mWaitingResult == NULL); // The debugger thread shouldn't be waiting on anything, it should be detached at this point
|
|
|
|
for (auto job : mThreadPool.mJobs)
|
|
{
|
|
auto netRequest = (NetRequest*)job;
|
|
netRequest->Cancel();
|
|
}
|
|
|
|
for (auto kv : mCachedResults)
|
|
{
|
|
NetResult* netResult = kv.mValue;
|
|
if (netResult->mCurRequest != NULL)
|
|
netResult->mRemoved = true;
|
|
else
|
|
delete netResult;
|
|
}
|
|
mCachedResults.Clear();
|
|
|
|
for (auto netResult : mOldResults)
|
|
{
|
|
delete netResult;
|
|
}
|
|
mOldResults.Clear();
|
|
}
|
|
|
|
void NetManager::CancelCurrent()
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
if (mWaitingRequest != NULL)
|
|
mWaitingRequest->Cancel();
|
|
else if ((mWaitingResult != NULL) && (mWaitingResult->mCurRequest != NULL))
|
|
mWaitingResult->mCurRequest->Cancel();
|
|
}
|
|
|
|
void NetManager::Cancel(NetResult* netResult)
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
BfLogDbg("NetManager cancel %s\n", netResult->mURL.c_str());
|
|
if (netResult->mCurRequest != NULL)
|
|
netResult->mCurRequest->Cancel();
|
|
}
|
|
|
|
void NetManager::SetCancelOnSuccess(NetResult* dependentResult, NetResult* cancelOnSucess)
|
|
{
|
|
AutoCrit autoCrit(mThreadPool.mCritSect);
|
|
if (dependentResult->mCurRequest != NULL)
|
|
{
|
|
dependentResult->mCurRequest->mCancelOnSuccess = cancelOnSucess;
|
|
}
|
|
} |