1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-07 19:18:19 +02:00
Beef/IDEHelper/NetManager.cpp
2022-07-26 13:27:03 -04:00

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;
}
}