mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-18 12:05:14 -04:00
Fix C# build being broken
This commit is contained in:
parent
dbdeb0d01b
commit
67558dfe7e
@ -92,7 +92,7 @@ namespace ClassicalSharp {
|
||||
float sensitivity = sensiFactor * game.MouseSensitivity;
|
||||
|
||||
if (game.SmoothCamera) {
|
||||
speedX += delta.X * adjust; game.LocalPlayer.ModelScale.XYZ
|
||||
speedX += delta.X * adjust;
|
||||
speedX *= slippery;
|
||||
speedY += delta.Y * adjust;
|
||||
speedY *= slippery;
|
||||
|
@ -1,335 +0,0 @@
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Platform.h"
|
||||
#include "Funcs.h"
|
||||
#include "Logger.h"
|
||||
#include "Stream.h"
|
||||
#include "GameStructs.h"
|
||||
|
||||
void ASyncRequest_Free(struct AsyncRequest* request) {
|
||||
Mem_Free(request->Data);
|
||||
request->Data = NULL;
|
||||
request->Size = 0;
|
||||
}
|
||||
|
||||
#define ASYNC_DEF_ELEMS 10
|
||||
struct AsyncRequestList {
|
||||
int MaxElems, Count;
|
||||
struct AsyncRequest* Requests;
|
||||
struct AsyncRequest DefaultRequests[ASYNC_DEF_ELEMS];
|
||||
};
|
||||
|
||||
static void AsyncRequestList_EnsureSpace(struct AsyncRequestList* list) {
|
||||
if (list->Count < list->MaxElems) return;
|
||||
list->Requests = Utils_Resize(list->Requests, &list->MaxElems,
|
||||
sizeof(struct AsyncRequest), ASYNC_DEF_ELEMS, 10);
|
||||
}
|
||||
|
||||
static void AsyncRequestList_Append(struct AsyncRequestList* list, struct AsyncRequest* item) {
|
||||
AsyncRequestList_EnsureSpace(list);
|
||||
list->Requests[list->Count++] = *item;
|
||||
}
|
||||
|
||||
static void AsyncRequestList_Prepend(struct AsyncRequestList* list, struct AsyncRequest* item) {
|
||||
int i;
|
||||
AsyncRequestList_EnsureSpace(list);
|
||||
|
||||
for (i = list->Count; i > 0; i--) {
|
||||
list->Requests[i] = list->Requests[i - 1];
|
||||
}
|
||||
list->Requests[0] = *item;
|
||||
list->Count++;
|
||||
}
|
||||
|
||||
static void AsyncRequestList_RemoveAt(struct AsyncRequestList* list, int i) {
|
||||
if (i < 0 || i >= list->Count) Logger_Abort("Tried to remove element at list end");
|
||||
|
||||
for (; i < list->Count - 1; i++) {
|
||||
list->Requests[i] = list->Requests[i + 1];
|
||||
}
|
||||
list->Count--;
|
||||
}
|
||||
|
||||
static void AsyncRequestList_Init(struct AsyncRequestList* list) {
|
||||
list->MaxElems = ASYNC_DEF_ELEMS;
|
||||
list->Count = 0;
|
||||
list->Requests = list->DefaultRequests;
|
||||
}
|
||||
|
||||
static void AsyncRequestList_Free(struct AsyncRequestList* list) {
|
||||
if (list->Requests != list->DefaultRequests) {
|
||||
Mem_Free(list->Requests);
|
||||
}
|
||||
AsyncRequestList_Init(list);
|
||||
}
|
||||
|
||||
static void* async_waitable;
|
||||
static void* async_workerThread;
|
||||
static void* async_pendingMutex;
|
||||
static void* async_processedMutex;
|
||||
static void* async_curRequestMutex;
|
||||
static volatile bool async_terminate;
|
||||
|
||||
static struct AsyncRequestList async_pending;
|
||||
static struct AsyncRequestList async_processed;
|
||||
const static String async_skinServer = String_FromConst("http://static.classicube.net/skins/");
|
||||
static struct AsyncRequest async_curRequest;
|
||||
static volatile int async_curProgress = ASYNC_PROGRESS_NOTHING;
|
||||
bool AsyncDownloader_Cookies;
|
||||
|
||||
static void AsyncDownloader_Add(const String* url, bool priority, const String* id, uint8_t type, TimeMS* lastModified, const String* etag, const String* data) {
|
||||
struct AsyncRequest req = { 0 };
|
||||
String reqUrl, reqID, reqEtag;
|
||||
|
||||
String_InitArray(reqUrl, req.URL);
|
||||
String_Copy(&reqUrl, url);
|
||||
String_InitArray(reqID, req.ID);
|
||||
String_Copy(&reqID, id);
|
||||
|
||||
req.RequestType = type;
|
||||
Platform_Log2("Adding %s (type %b)", &reqUrl, &type);
|
||||
|
||||
String_InitArray(reqEtag, req.Etag);
|
||||
if (lastModified) { req.LastModified = *lastModified; }
|
||||
if (etag) { String_Copy(&reqEtag, etag); }
|
||||
/* request.Data = data; TODO: Implement this. do we need to copy or expect caller to malloc it? */
|
||||
|
||||
Mutex_Lock(async_pendingMutex);
|
||||
{
|
||||
req.TimeAdded = DateTime_CurrentUTC_MS();
|
||||
if (priority) {
|
||||
AsyncRequestList_Prepend(&async_pending, &req);
|
||||
} else {
|
||||
AsyncRequestList_Append(&async_pending, &req);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(async_pendingMutex);
|
||||
Waitable_Signal(async_waitable);
|
||||
}
|
||||
|
||||
void AsyncDownloader_GetSkin(const String* id, const String* skinName) {
|
||||
String url; char urlBuffer[STRING_SIZE];
|
||||
String_InitArray(url, urlBuffer);
|
||||
|
||||
if (Utils_IsUrlPrefix(skinName, 0)) {
|
||||
String_Copy(&url, skinName);
|
||||
} else {
|
||||
String_AppendString(&url, &async_skinServer);
|
||||
String_AppendColorless(&url, skinName);
|
||||
String_AppendConst(&url, ".png");
|
||||
}
|
||||
|
||||
AsyncDownloader_Add(&url, false, id, REQUEST_TYPE_DATA, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void AsyncDownloader_GetData(const String* url, bool priority, const String* id) {
|
||||
AsyncDownloader_Add(url, priority, id, REQUEST_TYPE_DATA, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void AsyncDownloader_GetContentLength(const String* url, bool priority, const String* id) {
|
||||
AsyncDownloader_Add(url, priority, id, REQUEST_TYPE_CONTENT_LENGTH, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void AsyncDownloader_PostString(const String* url, bool priority, const String* id, const String* contents) {
|
||||
AsyncDownloader_Add(url, priority, id, REQUEST_TYPE_DATA, NULL, NULL, contents);
|
||||
}
|
||||
|
||||
void AsyncDownloader_GetDataEx(const String* url, bool priority, const String* id, TimeMS* lastModified, const String* etag) {
|
||||
AsyncDownloader_Add(url, priority, id, REQUEST_TYPE_DATA, lastModified, etag, NULL);
|
||||
}
|
||||
|
||||
void AsyncDownloader_PurgeOldEntriesTask(struct ScheduledTask* task) {
|
||||
struct AsyncRequest* item;
|
||||
int i;
|
||||
|
||||
Mutex_Lock(async_processedMutex);
|
||||
{
|
||||
TimeMS now = DateTime_CurrentUTC_MS();
|
||||
for (i = async_processed.Count - 1; i >= 0; i--) {
|
||||
item = &async_processed.Requests[i];
|
||||
if (item->TimeDownloaded + (10 * 1000) >= now) continue;
|
||||
|
||||
ASyncRequest_Free(item);
|
||||
AsyncRequestList_RemoveAt(&async_processed, i);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(async_processedMutex);
|
||||
}
|
||||
|
||||
static int AsyncRequestList_Find(const String* id, struct AsyncRequest* item) {
|
||||
String reqID;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < async_processed.Count; i++) {
|
||||
reqID = String_FromRawArray(async_processed.Requests[i].ID);
|
||||
if (!String_Equals(id, &reqID)) continue;
|
||||
|
||||
*item = async_processed.Requests[i];
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool AsyncDownloader_Get(const String* id, struct AsyncRequest* item) {
|
||||
int i;
|
||||
Mutex_Lock(async_processedMutex);
|
||||
{
|
||||
i = AsyncRequestList_Find(id, item);
|
||||
if (i >= 0) AsyncRequestList_RemoveAt(&async_processed, i);
|
||||
}
|
||||
Mutex_Unlock(async_processedMutex);
|
||||
return i >= 0;
|
||||
}
|
||||
|
||||
bool AsyncDownloader_GetCurrent(struct AsyncRequest* request, int* progress) {
|
||||
Mutex_Lock(async_curRequestMutex);
|
||||
{
|
||||
*request = async_curRequest;
|
||||
*progress = async_curProgress;
|
||||
}
|
||||
Mutex_Unlock(async_curRequestMutex);
|
||||
return request->ID[0];
|
||||
}
|
||||
|
||||
void AsyncDownloader_Clear(void) {
|
||||
Mutex_Lock(async_pendingMutex);
|
||||
{
|
||||
AsyncRequestList_Free(&async_pending);
|
||||
}
|
||||
Mutex_Unlock(async_pendingMutex);
|
||||
Waitable_Signal(async_waitable);
|
||||
}
|
||||
|
||||
static void AsyncDownloader_ProcessRequest(struct AsyncRequest* request) {
|
||||
String url = String_FromRawArray(request->URL);
|
||||
uint64_t beg, end;
|
||||
uint32_t size, elapsed;
|
||||
uintptr_t addr;
|
||||
|
||||
Platform_Log2("Downloading from %s (type %b)", &url, &request->RequestType);
|
||||
beg = Stopwatch_Measure();
|
||||
request->Result = Http_Do(request, &async_curProgress);
|
||||
end = Stopwatch_Measure();
|
||||
|
||||
elapsed = Stopwatch_ElapsedMicroseconds(beg, end) / 1000;
|
||||
Platform_Log3("HTTP: return code %i (http %i), in %i ms",
|
||||
&request->Result, &request->StatusCode, &elapsed);
|
||||
|
||||
if (request->Data) {
|
||||
size = request->Size;
|
||||
addr = (uintptr_t)request->Data;
|
||||
Platform_Log2("HTTP returned data: %i bytes at %x", &size, &addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void AsyncDownloader_CompleteResult(struct AsyncRequest* request) {
|
||||
struct AsyncRequest older;
|
||||
String id = String_FromRawArray(request->ID);
|
||||
int index;
|
||||
request->TimeDownloaded = DateTime_CurrentUTC_MS();
|
||||
|
||||
index = AsyncRequestList_Find(&id, &older);
|
||||
if (index >= 0) {
|
||||
/* very rare case - priority item was inserted, then inserted again (so put before first item), */
|
||||
/* and both items got downloaded before an external function removed them from the queue */
|
||||
if (older.TimeAdded > request->TimeAdded) {
|
||||
ASyncRequest_Free(request);
|
||||
} else {
|
||||
/* normal case, replace older request */
|
||||
ASyncRequest_Free(&older);
|
||||
async_processed.Requests[index] = *request;
|
||||
}
|
||||
} else {
|
||||
AsyncRequestList_Append(&async_processed, request);
|
||||
}
|
||||
}
|
||||
|
||||
static void AsyncDownloader_WorkerFunc(void) {
|
||||
struct AsyncRequest request;
|
||||
bool hasRequest, stop;
|
||||
|
||||
for (;;) {
|
||||
hasRequest = false;
|
||||
|
||||
Mutex_Lock(async_pendingMutex);
|
||||
{
|
||||
stop = async_terminate;
|
||||
if (!stop && async_pending.Count) {
|
||||
request = async_pending.Requests[0];
|
||||
hasRequest = true;
|
||||
AsyncRequestList_RemoveAt(&async_pending, 0);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(async_pendingMutex);
|
||||
|
||||
if (stop) return;
|
||||
/* Block until another thread submits a request to do */
|
||||
if (!hasRequest) {
|
||||
Platform_LogConst("Going back to sleep...");
|
||||
Waitable_Wait(async_waitable);
|
||||
continue;
|
||||
}
|
||||
|
||||
Platform_LogConst("Got something to do!");
|
||||
Mutex_Lock(async_curRequestMutex);
|
||||
{
|
||||
async_curRequest = request;
|
||||
async_curProgress = ASYNC_PROGRESS_MAKING_REQUEST;
|
||||
}
|
||||
Mutex_Unlock(async_curRequestMutex);
|
||||
|
||||
Platform_LogConst("Doing it");
|
||||
/* performing request doesn't need thread safety */
|
||||
AsyncDownloader_ProcessRequest(&request);
|
||||
|
||||
Mutex_Lock(async_processedMutex);
|
||||
{
|
||||
AsyncDownloader_CompleteResult(&request);
|
||||
}
|
||||
Mutex_Unlock(async_processedMutex);
|
||||
|
||||
Mutex_Lock(async_curRequestMutex);
|
||||
{
|
||||
async_curRequest.ID[0] = '\0';
|
||||
async_curProgress = ASYNC_PROGRESS_NOTHING;
|
||||
}
|
||||
Mutex_Unlock(async_curRequestMutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*------------------------------------------------AsyncDownloader component------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static void AsyncDownloader_Init(void) {
|
||||
ScheduledTask_Add(30, AsyncDownloader_PurgeOldEntriesTask);
|
||||
AsyncRequestList_Init(&async_pending);
|
||||
AsyncRequestList_Init(&async_processed);
|
||||
Http_Init();
|
||||
|
||||
async_waitable = Waitable_Create();
|
||||
async_pendingMutex = Mutex_Create();
|
||||
async_processedMutex = Mutex_Create();
|
||||
async_curRequestMutex = Mutex_Create();
|
||||
async_workerThread = Thread_Start(AsyncDownloader_WorkerFunc, false);
|
||||
}
|
||||
|
||||
static void AsyncDownloader_Free(void) {
|
||||
async_terminate = true;
|
||||
AsyncDownloader_Clear();
|
||||
Thread_Join(async_workerThread);
|
||||
|
||||
AsyncRequestList_Free(&async_pending);
|
||||
AsyncRequestList_Free(&async_processed);
|
||||
Http_Free();
|
||||
|
||||
Waitable_Free(async_waitable);
|
||||
Mutex_Free(async_pendingMutex);
|
||||
Mutex_Free(async_processedMutex);
|
||||
Mutex_Free(async_curRequestMutex);
|
||||
}
|
||||
|
||||
struct IGameComponent AsyncDownloader_Component = {
|
||||
AsyncDownloader_Init, /* Init */
|
||||
AsyncDownloader_Free, /* Free */
|
||||
AsyncDownloader_Clear /* Reset */
|
||||
};
|
@ -1,63 +0,0 @@
|
||||
#ifndef CC_ASYNCDOWNLOADER_H
|
||||
#define CC_ASYNCDOWNLOADER_H
|
||||
#include "Constants.h"
|
||||
#include "Utils.h"
|
||||
/* Downloads images, texture packs, skins, etc in async manner.
|
||||
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
*/
|
||||
struct IGameComponent;
|
||||
struct ScheduledTask;
|
||||
#define URL_MAX_SIZE (STRING_SIZE * 2)
|
||||
|
||||
extern struct IGameComponent AsyncDownloader_Component;
|
||||
/* TODO: Implement these */
|
||||
extern bool AsyncDownloader_Cookies;
|
||||
/* TODO: Connection pooling */
|
||||
|
||||
enum REQUEST_TYPE { REQUEST_TYPE_DATA, REQUEST_TYPE_CONTENT_LENGTH };
|
||||
enum AsyncProgress {
|
||||
ASYNC_PROGRESS_NOTHING = -3,
|
||||
ASYNC_PROGRESS_MAKING_REQUEST = -2,
|
||||
ASYNC_PROGRESS_FETCHING_DATA = -1
|
||||
};
|
||||
|
||||
struct AsyncRequest {
|
||||
char URL[URL_MAX_SIZE]; /* URL data is downloaded from/uploaded to. */
|
||||
char ID[STRING_SIZE]; /* Unique identifier for this request. */
|
||||
TimeMS TimeAdded; /* Time this request was added to queue of requests. */
|
||||
TimeMS TimeDownloaded; /* Time response contents were completely downloaded. */
|
||||
int StatusCode; /* HTTP status code returned in the response. */
|
||||
uint32_t ContentLength; /* HTTP content length returned in the response. */
|
||||
|
||||
ReturnCode Result; /* 0 on success, otherwise platform-specific error. */
|
||||
void* Data; /* Contents of the response. */
|
||||
uint32_t Size; /* Size of the contents. */
|
||||
|
||||
TimeMS LastModified; /* Time item cached at (if at all) */
|
||||
char Etag[STRING_SIZE]; /* ETag of cached item (if any) */
|
||||
uint8_t RequestType; /* Whether to fetch contents or just headers. */
|
||||
};
|
||||
|
||||
void ASyncRequest_Free(struct AsyncRequest* request);
|
||||
|
||||
void AsyncDownloader_GetSkin(const String* id, const String* skinName);
|
||||
/* Asynchronously downloads contents of a webpage. */
|
||||
void AsyncDownloader_GetData(const String* url, bool priority, const String* id);
|
||||
/* Asynchronously downloads Content Length header of a webpage. */
|
||||
void AsyncDownloader_GetContentLength(const String* url, bool priority, const String* id);
|
||||
/* TODO: Implement post */
|
||||
void AsyncDownloader_UNSAFE_PostData(const String* url, bool priority, const String* id, const void* data, const uint32_t size);
|
||||
/* Asynchronously downloads contents of a webpage. */
|
||||
/* Optionally also sets If-Modified-Since and If-None-Match headers. */
|
||||
void AsyncDownloader_GetDataEx(const String* url, bool priority, const String* id, TimeMS* lastModified, const String* etag);
|
||||
|
||||
/* Attempts to retrieve a fully completed request. */
|
||||
/* Returns whether a request with a matching id was retrieved. */
|
||||
bool AsyncDownloader_Get(const String* id, struct AsyncRequest* item);
|
||||
/* Retrieves information about the request currently being processed. */
|
||||
/* Returns whether there is actually a request being currently processed. */
|
||||
bool AsyncDownloader_GetCurrent(struct AsyncRequest* request, int* progress);
|
||||
/* Clears the list of pending requests. */
|
||||
void AsyncDownloader_Clear(void);
|
||||
void AsyncDownloader_PurgeOldEntriesTask(struct ScheduledTask* task);
|
||||
#endif
|
@ -187,7 +187,7 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AsyncDownloader.h" />
|
||||
<ClInclude Include="Http.h" />
|
||||
<ClInclude Include="Audio.h" />
|
||||
<ClInclude Include="AxisLinesRenderer.h" />
|
||||
<ClInclude Include="BlockID.h" />
|
||||
@ -253,7 +253,7 @@
|
||||
<ClInclude Include="World.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AsyncDownloader.c" />
|
||||
<ClCompile Include="Http.c" />
|
||||
<ClCompile Include="Audio.c" />
|
||||
<ClCompile Include="Camera.c" />
|
||||
<ClCompile Include="AxisLinesRenderer.c" />
|
||||
|
@ -234,9 +234,6 @@
|
||||
<ClInclude Include="ServerConnection.h">
|
||||
<Filter>Header Files\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AsyncDownloader.h">
|
||||
<Filter>Header Files\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Event.h">
|
||||
<Filter>Header Files\Game</Filter>
|
||||
</ClInclude>
|
||||
@ -318,6 +315,9 @@
|
||||
<ClInclude Include="Logger.h">
|
||||
<Filter>Header Files\Platform</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Http.h">
|
||||
<Filter>Header Files\Network</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="String.c">
|
||||
@ -440,9 +440,6 @@
|
||||
<ClCompile Include="PacketHandlers.c">
|
||||
<Filter>Source Files\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AsyncDownloader.c">
|
||||
<Filter>Source Files\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EnvRenderer.c">
|
||||
<Filter>Source Files\Rendering</Filter>
|
||||
</ClCompile>
|
||||
@ -566,5 +563,8 @@
|
||||
<ClCompile Include="Logger.c">
|
||||
<Filter>Source Files\Platform</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Http.c">
|
||||
<Filter>Source Files\Network</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -11,7 +11,7 @@
|
||||
#include "Lighting.h"
|
||||
#include "Drawer2D.h"
|
||||
#include "Particle.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Chat.h"
|
||||
#include "Model.h"
|
||||
#include "Input.h"
|
||||
@ -688,7 +688,7 @@ static void Player_CheckSkin(struct Player* p) {
|
||||
struct Player* first;
|
||||
String url, skin = String_FromRawArray(e->SkinNameRaw);
|
||||
|
||||
struct AsyncRequest item;
|
||||
struct HttpRequest item;
|
||||
struct Stream mem;
|
||||
Bitmap bmp;
|
||||
ReturnCode res;
|
||||
@ -696,14 +696,14 @@ static void Player_CheckSkin(struct Player* p) {
|
||||
if (!p->FetchedSkin && e->Model->UsesSkin) {
|
||||
first = Player_FirstOtherWithSameSkinAndFetchedSkin(p);
|
||||
if (!first) {
|
||||
AsyncDownloader_GetSkin(&skin, &skin);
|
||||
Http_AsyncGetSkin(&skin, &skin);
|
||||
} else {
|
||||
Player_CopySkin(p, first);
|
||||
}
|
||||
p->FetchedSkin = true;
|
||||
}
|
||||
|
||||
if (!AsyncDownloader_Get(&skin, &item)) return;
|
||||
if (!Http_GetResult(&skin, &item)) return;
|
||||
if (!item.Data) { Player_SetSkinAll(p, true); return; }
|
||||
Stream_ReadonlyMemory(&mem, item.Data, item.Size);
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "Drawer2D.h"
|
||||
#include "Model.h"
|
||||
#include "Particle.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Inventory.h"
|
||||
#include "InputHandler.h"
|
||||
#include "ServerConnection.h"
|
||||
@ -459,7 +459,7 @@ static void Game_Load(void) {
|
||||
|
||||
Game_AddComponent(&Models_Component);
|
||||
Game_AddComponent(&Entities_Component);
|
||||
Game_AddComponent(&AsyncDownloader_Component);
|
||||
Game_AddComponent(&Http_Component);
|
||||
Game_AddComponent(&Lighting_Component);
|
||||
|
||||
Game_AddComponent(&Animations_Component);
|
||||
|
637
src/Http.c
Normal file
637
src/Http.c
Normal file
@ -0,0 +1,637 @@
|
||||
#include "Http.h"
|
||||
#include "Platform.h"
|
||||
#include "Funcs.h"
|
||||
#include "Logger.h"
|
||||
#include "Stream.h"
|
||||
#include "GameStructs.h"
|
||||
|
||||
#ifdef CC_BUILD_WIN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOSERVICE
|
||||
#define NOMCX
|
||||
#define NOIME
|
||||
#define _WIN32_IE 0x0400
|
||||
#define WINVER 0x0500
|
||||
#define _WIN32_WINNT 0x0500
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <wininet.h>
|
||||
|
||||
#define HTTP_QUERY_ETAG 54 /* Missing from some old MingW32 headers */
|
||||
#endif
|
||||
/* POSIX is mainly shared between Linux and OSX */
|
||||
#ifdef CC_BUILD_POSIX
|
||||
#include <curl/curl.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
void HttpRequest_Free(struct HttpRequest* request) {
|
||||
Mem_Free(request->Data);
|
||||
request->Data = NULL;
|
||||
request->Size = 0;
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------System/Native interface-------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
#ifdef CC_BUILD_WIN
|
||||
static HINTERNET hInternet;
|
||||
/* TODO: Test last modified and etag even work */
|
||||
#define FLAG_STATUS HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER
|
||||
#define FLAG_LENGTH HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER
|
||||
#define FLAG_LASTMOD HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME
|
||||
|
||||
static void Http_SysInit(void) {
|
||||
/* TODO: Should we use INTERNET_OPEN_TYPE_PRECONFIG instead? */
|
||||
hInternet = InternetOpenA(GAME_APP_NAME, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
||||
if (!hInternet) Logger_Abort2(GetLastError(), "Failed to init WinINet");
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysMake(struct HttpRequest* req, HINTERNET* handle) {
|
||||
String url = String_FromRawArray(req->URL);
|
||||
char urlStr[URL_MAX_SIZE + 1];
|
||||
Mem_Copy(urlStr, url.buffer, url.length);
|
||||
|
||||
urlStr[url.length] = '\0';
|
||||
char headersBuffer[STRING_SIZE * 3];
|
||||
String headers = String_FromArray(headersBuffer);
|
||||
|
||||
/* https://stackoverflow.com/questions/25308488/c-wininet-custom-http-headers */
|
||||
if (req->Etag[0] || req->LastModified) {
|
||||
if (req->LastModified) {
|
||||
String_AppendConst(&headers, "If-Modified-Since: ");
|
||||
DateTime_HttpDate(req->LastModified, &headers);
|
||||
String_AppendConst(&headers, "\r\n");
|
||||
}
|
||||
|
||||
if (req->Etag[0]) {
|
||||
String etag = String_FromRawArray(req->Etag);
|
||||
String_AppendConst(&headers, "If-None-Match: ");
|
||||
String_AppendString(&headers, &etag);
|
||||
String_AppendConst(&headers, "\r\n");
|
||||
}
|
||||
|
||||
String_AppendConst(&headers, "\r\n\r\n");
|
||||
headers.buffer[headers.length] = '\0';
|
||||
} else { headers.buffer = NULL; }
|
||||
|
||||
*handle = InternetOpenUrlA(hInternet, urlStr, headers.buffer, headers.length,
|
||||
INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD, 0);
|
||||
return *handle ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysGetHeaders(struct HttpRequest* req, HINTERNET handle) {
|
||||
DWORD len;
|
||||
|
||||
len = sizeof(DWORD);
|
||||
if (!HttpQueryInfoA(handle, FLAG_STATUS, &req->StatusCode, &len, NULL)) return GetLastError();
|
||||
|
||||
len = sizeof(DWORD);
|
||||
HttpQueryInfoA(handle, FLAG_LENGTH, &req->ContentLength, &len, NULL);
|
||||
|
||||
SYSTEMTIME sysTime;
|
||||
len = sizeof(SYSTEMTIME);
|
||||
if (HttpQueryInfoA(handle, FLAG_LASTMOD, &sysTime, &len, NULL)) {
|
||||
struct DateTime time;
|
||||
time.Year = sysTime.wYear; time.Month = sysTime.wMonth;
|
||||
time.Day = sysTime.wDay; time.Hour = sysTime.wHour;
|
||||
time.Minute = sysTime.wMinute; time.Second = sysTime.wSecond;
|
||||
time.Milli = sysTime.wMilliseconds;
|
||||
req->LastModified = DateTime_TotalMs(&time);
|
||||
}
|
||||
|
||||
String etag = String_ClearedArray(req->Etag);
|
||||
len = etag.capacity;
|
||||
HttpQueryInfoA(handle, HTTP_QUERY_ETAG, etag.buffer, &len, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysGetData(struct HttpRequest* req, HINTERNET handle, volatile int* progress) {
|
||||
uint8_t* buffer;
|
||||
uint32_t size, totalRead;
|
||||
uint32_t read, avail;
|
||||
bool success;
|
||||
|
||||
*progress = 0;
|
||||
size = req->ContentLength ? req->ContentLength : 1;
|
||||
buffer = Mem_Alloc(size, 1, "http get data");
|
||||
totalRead = 0;
|
||||
|
||||
req->Data = buffer;
|
||||
req->Size = 0;
|
||||
|
||||
for (;;) {
|
||||
if (!InternetQueryDataAvailable(handle, &avail, 0, 0)) break;
|
||||
if (!avail) break;
|
||||
|
||||
/* expand if buffer is too small (some servers don't give content length) */
|
||||
if (totalRead + avail > size) {
|
||||
size = totalRead + avail;
|
||||
buffer = Mem_Realloc(buffer, size, 1, "http inc data");
|
||||
req->Data = buffer;
|
||||
}
|
||||
|
||||
success = InternetReadFile(handle, &buffer[totalRead], avail, &read);
|
||||
if (!success) { Mem_Free(buffer); return GetLastError(); }
|
||||
if (!read) break;
|
||||
|
||||
totalRead += read;
|
||||
if (req->ContentLength) *progress = (int)(100.0f * totalRead / size);
|
||||
req->Size += read;
|
||||
}
|
||||
|
||||
*progress = 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysDo(struct HttpRequest* req, volatile int* progress) {
|
||||
HINTERNET handle;
|
||||
ReturnCode res = Http_SysMake(req, &handle);
|
||||
if (res) return res;
|
||||
|
||||
*progress = ASYNC_PROGRESS_FETCHING_DATA;
|
||||
res = Http_SysGetHeaders(req, handle);
|
||||
if (res) { InternetCloseHandle(handle); return res; }
|
||||
|
||||
if (req->RequestType != REQUEST_TYPE_HEAD && req->StatusCode == 200) {
|
||||
res = Http_SysGetData(req, handle, progress);
|
||||
if (res) { InternetCloseHandle(handle); return res; }
|
||||
}
|
||||
|
||||
return InternetCloseHandle(handle) ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysFree(void) {
|
||||
return InternetCloseHandle(hInternet) ? 0 : GetLastError();
|
||||
}
|
||||
#endif
|
||||
#ifdef CC_BUILD_POSIX
|
||||
CURL* curl;
|
||||
|
||||
static void Http_SysInit(void) {
|
||||
CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
if (res) Logger_Abort2(res, "Failed to init curl");
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) Logger_Abort("Failed to init easy curl");
|
||||
}
|
||||
|
||||
static int Http_SysProgress(int* progress, double total, double received, double a, double b) {
|
||||
if (total == 0) return 0;
|
||||
*progress = (int)(100 * received / total);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct curl_slist* Http_SysMake(struct HttpRequest* req) {
|
||||
String tmp; char buffer[STRING_SIZE + 1];
|
||||
struct curl_slist* list = NULL;
|
||||
String etag;
|
||||
|
||||
if (req->Etag[0]) {
|
||||
String_InitArray_NT(tmp, buffer);
|
||||
String_AppendConst(&tmp, "If-None-Match: ");
|
||||
|
||||
etag = String_FromRawArray(req->Etag);
|
||||
String_AppendString(&tmp, &etag);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
list = curl_slist_append(list, tmp.buffer);
|
||||
}
|
||||
|
||||
if (req->LastModified) {
|
||||
String_InitArray_NT(tmp, buffer);
|
||||
String_AppendConst(&tmp, "Last-Modified: ");
|
||||
|
||||
DateTime_HttpDate(req->LastModified, &tmp);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
list = curl_slist_append(list, tmp.buffer);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static size_t Http_SysGetHeaders(char *buffer, size_t size, size_t nitems, struct HttpRequest* req) {
|
||||
String tmp; char tmpBuffer[STRING_SIZE + 1];
|
||||
String line, name, value;
|
||||
time_t time;
|
||||
|
||||
if (size != 1) return size * nitems; /* non byte header */
|
||||
line = String_Init(buffer, nitems, nitems);
|
||||
if (!String_UNSAFE_Separate(&line, ':', &name, &value)) return nitems;
|
||||
|
||||
/* value usually has \r\n at end */
|
||||
if (value.length && value.buffer[value.length - 1] == '\n') value.length--;
|
||||
if (value.length && value.buffer[value.length - 1] == '\r') value.length--;
|
||||
if (!value.length) return nitems;
|
||||
|
||||
if (String_CaselessEqualsConst(&name, "ETag")) {
|
||||
tmp = String_ClearedArray(req->Etag);
|
||||
String_AppendString(&tmp, &value);
|
||||
} else if (String_CaselessEqualsConst(&name, "Content-Length")) {
|
||||
Convert_ParseInt(&value, &req->ContentLength);
|
||||
/* TODO: Fix when ContentLength isn't RequestSize */
|
||||
req->Size = req->ContentLength;
|
||||
} else if (String_CaselessEqualsConst(&name, "Last-Modified")) {
|
||||
String_InitArray_NT(tmp, tmpBuffer);
|
||||
String_AppendString(&tmp, &value);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
|
||||
time = curl_getdate(tmp.buffer, NULL);
|
||||
if (time == -1) return nitems;
|
||||
req->LastModified = (uint64_t)time * 1000 + UNIX_EPOCH;
|
||||
}
|
||||
return nitems;
|
||||
}
|
||||
|
||||
static size_t Http_SysGetData(char *buffer, size_t size, size_t nitems, struct HttpRequest* req) {
|
||||
uint32_t total, left;
|
||||
uint8_t* dst;
|
||||
|
||||
total = req->Size;
|
||||
if (!total || req->RequestType == REQUEST_TYPE_HEAD) return 0;
|
||||
if (!req->Data) req->Data = Mem_Alloc(total, 1, "http get data");
|
||||
|
||||
/* reuse Result as an offset */
|
||||
left = total - req->Result;
|
||||
left = min(left, nitems);
|
||||
dst = (uint8_t*)req->Data + req->Result;
|
||||
|
||||
Mem_Copy(dst, buffer, left);
|
||||
req->Result += left;
|
||||
return nitems;
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysDo(struct HttpRequest* req, volatile int* progress) {
|
||||
struct curl_slist* list;
|
||||
String url = String_FromRawArray(req->URL);
|
||||
char urlStr[600];
|
||||
long status = 0;
|
||||
CURLcode res;
|
||||
|
||||
Platform_ConvertString(urlStr, &url);
|
||||
curl_easy_reset(curl);
|
||||
list = Http_SysMake(req);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, urlStr);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, GAME_APP_NAME);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, Http_SysProgress);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, progress);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, Http_SysGetHeaders);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, req);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Http_SysGetData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
|
||||
|
||||
*progress = ASYNC_PROGRESS_FETCHING_DATA;
|
||||
res = curl_easy_perform(curl);
|
||||
*progress = 100;
|
||||
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
req->StatusCode = status;
|
||||
|
||||
curl_slist_free_all(list);
|
||||
return res;
|
||||
}
|
||||
|
||||
static ReturnCode Http_SysFree(void) {
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------Asynchronous downloader-------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
#define HTTP_DEF_ELEMS 10
|
||||
struct HttpRequestList {
|
||||
int MaxElems, Count;
|
||||
struct HttpRequest* Requests;
|
||||
struct HttpRequest DefaultRequests[HTTP_DEF_ELEMS];
|
||||
};
|
||||
|
||||
static void HttpRequestList_EnsureSpace(struct HttpRequestList* list) {
|
||||
if (list->Count < list->MaxElems) return;
|
||||
list->Requests = Utils_Resize(list->Requests, &list->MaxElems,
|
||||
sizeof(struct HttpRequest), HTTP_DEF_ELEMS, 10);
|
||||
}
|
||||
|
||||
static void HttpRequestList_Append(struct HttpRequestList* list, struct HttpRequest* item) {
|
||||
HttpRequestList_EnsureSpace(list);
|
||||
list->Requests[list->Count++] = *item;
|
||||
}
|
||||
|
||||
static void HttpRequestList_Prepend(struct HttpRequestList* list, struct HttpRequest* item) {
|
||||
int i;
|
||||
HttpRequestList_EnsureSpace(list);
|
||||
|
||||
for (i = list->Count; i > 0; i--) {
|
||||
list->Requests[i] = list->Requests[i - 1];
|
||||
}
|
||||
list->Requests[0] = *item;
|
||||
list->Count++;
|
||||
}
|
||||
|
||||
static void HttpRequestList_RemoveAt(struct HttpRequestList* list, int i) {
|
||||
if (i < 0 || i >= list->Count) Logger_Abort("Tried to remove element at list end");
|
||||
|
||||
for (; i < list->Count - 1; i++) {
|
||||
list->Requests[i] = list->Requests[i + 1];
|
||||
}
|
||||
list->Count--;
|
||||
}
|
||||
|
||||
static void HttpRequestList_Init(struct HttpRequestList* list) {
|
||||
list->MaxElems = HTTP_DEF_ELEMS;
|
||||
list->Count = 0;
|
||||
list->Requests = list->DefaultRequests;
|
||||
}
|
||||
|
||||
static void HttpRequestList_Free(struct HttpRequestList* list) {
|
||||
if (list->Requests != list->DefaultRequests) {
|
||||
Mem_Free(list->Requests);
|
||||
}
|
||||
HttpRequestList_Init(list);
|
||||
}
|
||||
|
||||
static void* http_waitable;
|
||||
static void* http_workerThread;
|
||||
static void* http_pendingMutex;
|
||||
static void* http_processedMutex;
|
||||
static void* http_curRequestMutex;
|
||||
static volatile bool http_terminate;
|
||||
|
||||
static struct HttpRequestList http_pending;
|
||||
static struct HttpRequestList http_processed;
|
||||
const static String http_skinServer = String_FromConst("http://static.classicube.net/skins/");
|
||||
static struct HttpRequest http_curRequest;
|
||||
static volatile int http_curProgress = ASYNC_PROGRESS_NOTHING;
|
||||
bool Http_UseCookies;
|
||||
|
||||
/* Adds a request to the list of pending requests, waking up worker thread if needed. */
|
||||
static void Http_Add(const String* url, bool priority, const String* id, uint8_t type, TimeMS* lastModified, const String* etag, const String* data) {
|
||||
struct HttpRequest req = { 0 };
|
||||
String reqUrl, reqID, reqEtag;
|
||||
|
||||
String_InitArray(reqUrl, req.URL);
|
||||
String_Copy(&reqUrl, url);
|
||||
String_InitArray(reqID, req.ID);
|
||||
String_Copy(&reqID, id);
|
||||
|
||||
req.RequestType = type;
|
||||
Platform_Log2("Adding %s (type %b)", &reqUrl, &type);
|
||||
|
||||
String_InitArray(reqEtag, req.Etag);
|
||||
if (lastModified) { req.LastModified = *lastModified; }
|
||||
if (etag) { String_Copy(&reqEtag, etag); }
|
||||
/* request.Data = data; TODO: Implement this. do we need to copy or expect caller to malloc it? */
|
||||
|
||||
Mutex_Lock(http_pendingMutex);
|
||||
{
|
||||
req.TimeAdded = DateTime_CurrentUTC_MS();
|
||||
if (priority) {
|
||||
HttpRequestList_Prepend(&http_pending, &req);
|
||||
} else {
|
||||
HttpRequestList_Append(&http_pending, &req);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(http_pendingMutex);
|
||||
Waitable_Signal(http_waitable);
|
||||
}
|
||||
|
||||
void Http_AsyncGetSkin(const String* id, const String* skinName) {
|
||||
String url; char urlBuffer[STRING_SIZE];
|
||||
String_InitArray(url, urlBuffer);
|
||||
|
||||
if (Utils_IsUrlPrefix(skinName, 0)) {
|
||||
String_Copy(&url, skinName);
|
||||
} else {
|
||||
String_AppendString(&url, &http_skinServer);
|
||||
String_AppendColorless(&url, skinName);
|
||||
String_AppendConst(&url, ".png");
|
||||
}
|
||||
|
||||
Http_Add(&url, false, id, REQUEST_TYPE_GET, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void Http_AsyncGetData(const String* url, bool priority, const String* id) {
|
||||
Http_Add(url, priority, id, REQUEST_TYPE_GET, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void Http_AsyncGetHeaders(const String* url, bool priority, const String* id) {
|
||||
Http_Add(url, priority, id, REQUEST_TYPE_HEAD, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void AsyncDownloader_PostString(const String* url, bool priority, const String* id, const String* contents) {
|
||||
Http_Add(url, priority, id, REQUEST_TYPE_GET, NULL, NULL, contents);
|
||||
}
|
||||
|
||||
void Http_AsyncGetDataEx(const String* url, bool priority, const String* id, TimeMS* lastModified, const String* etag) {
|
||||
Http_Add(url, priority, id, REQUEST_TYPE_GET, lastModified, etag, NULL);
|
||||
}
|
||||
|
||||
void Http_PurgeOldEntriesTask(struct ScheduledTask* task) {
|
||||
struct HttpRequest* item;
|
||||
int i;
|
||||
|
||||
Mutex_Lock(http_processedMutex);
|
||||
{
|
||||
TimeMS now = DateTime_CurrentUTC_MS();
|
||||
for (i = http_processed.Count - 1; i >= 0; i--) {
|
||||
item = &http_processed.Requests[i];
|
||||
if (item->TimeDownloaded + (10 * 1000) >= now) continue;
|
||||
|
||||
HttpRequest_Free(item);
|
||||
HttpRequestList_RemoveAt(&http_processed, i);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(http_processedMutex);
|
||||
}
|
||||
|
||||
static int HttpRequestList_Find(const String* id, struct HttpRequest* item) {
|
||||
String reqID;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < http_processed.Count; i++) {
|
||||
reqID = String_FromRawArray(http_processed.Requests[i].ID);
|
||||
if (!String_Equals(id, &reqID)) continue;
|
||||
|
||||
*item = http_processed.Requests[i];
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Http_GetResult(const String* id, struct HttpRequest* item) {
|
||||
int i;
|
||||
Mutex_Lock(http_processedMutex);
|
||||
{
|
||||
i = HttpRequestList_Find(id, item);
|
||||
if (i >= 0) HttpRequestList_RemoveAt(&http_processed, i);
|
||||
}
|
||||
Mutex_Unlock(http_processedMutex);
|
||||
return i >= 0;
|
||||
}
|
||||
|
||||
bool Http_GetCurrent(struct HttpRequest* request, int* progress) {
|
||||
Mutex_Lock(http_curRequestMutex);
|
||||
{
|
||||
*request = http_curRequest;
|
||||
*progress = http_curProgress;
|
||||
}
|
||||
Mutex_Unlock(http_curRequestMutex);
|
||||
return request->ID[0];
|
||||
}
|
||||
|
||||
void Http_ClearPending(void) {
|
||||
Mutex_Lock(http_pendingMutex);
|
||||
{
|
||||
HttpRequestList_Free(&http_pending);
|
||||
}
|
||||
Mutex_Unlock(http_pendingMutex);
|
||||
Waitable_Signal(http_waitable);
|
||||
}
|
||||
|
||||
static void Http_ProcessRequest(struct HttpRequest* request) {
|
||||
String url = String_FromRawArray(request->URL);
|
||||
uint64_t beg, end;
|
||||
uint32_t size, elapsed;
|
||||
uintptr_t addr;
|
||||
|
||||
Platform_Log2("Downloading from %s (type %b)", &url, &request->RequestType);
|
||||
beg = Stopwatch_Measure();
|
||||
request->Result = Http_SysDo(request, &http_curProgress);
|
||||
end = Stopwatch_Measure();
|
||||
|
||||
elapsed = Stopwatch_ElapsedMicroseconds(beg, end) / 1000;
|
||||
Platform_Log3("HTTP: return code %i (http %i), in %i ms",
|
||||
&request->Result, &request->StatusCode, &elapsed);
|
||||
|
||||
if (request->Data) {
|
||||
size = request->Size;
|
||||
addr = (uintptr_t)request->Data;
|
||||
Platform_Log2("HTTP returned data: %i bytes at %x", &size, &addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void Http_CompleteResult(struct HttpRequest* request) {
|
||||
struct HttpRequest older;
|
||||
String id = String_FromRawArray(request->ID);
|
||||
int index;
|
||||
request->TimeDownloaded = DateTime_CurrentUTC_MS();
|
||||
|
||||
index = HttpRequestList_Find(&id, &older);
|
||||
if (index >= 0) {
|
||||
/* very rare case - priority item was inserted, then inserted again (so put before first item), */
|
||||
/* and both items got downloaded before an external function removed them from the queue */
|
||||
if (older.TimeAdded > request->TimeAdded) {
|
||||
HttpRequest_Free(request);
|
||||
} else {
|
||||
/* normal case, replace older request */
|
||||
HttpRequest_Free(&older);
|
||||
http_processed.Requests[index] = *request;
|
||||
}
|
||||
} else {
|
||||
HttpRequestList_Append(&http_processed, request);
|
||||
}
|
||||
}
|
||||
|
||||
static void Http_WorkerLoop(void) {
|
||||
struct HttpRequest request;
|
||||
bool hasRequest, stop;
|
||||
|
||||
for (;;) {
|
||||
hasRequest = false;
|
||||
|
||||
Mutex_Lock(http_pendingMutex);
|
||||
{
|
||||
stop = http_terminate;
|
||||
if (!stop && http_pending.Count) {
|
||||
request = http_pending.Requests[0];
|
||||
hasRequest = true;
|
||||
HttpRequestList_RemoveAt(&http_pending, 0);
|
||||
}
|
||||
}
|
||||
Mutex_Unlock(http_pendingMutex);
|
||||
|
||||
if (stop) return;
|
||||
/* Block until another thread submits a request to do */
|
||||
if (!hasRequest) {
|
||||
Platform_LogConst("Going back to sleep...");
|
||||
Waitable_Wait(http_waitable);
|
||||
continue;
|
||||
}
|
||||
|
||||
Platform_LogConst("Got something to do!");
|
||||
Mutex_Lock(http_curRequestMutex);
|
||||
{
|
||||
http_curRequest = request;
|
||||
http_curProgress = ASYNC_PROGRESS_MAKING_REQUEST;
|
||||
}
|
||||
Mutex_Unlock(http_curRequestMutex);
|
||||
|
||||
Platform_LogConst("Doing it");
|
||||
/* performing request doesn't need thread safety */
|
||||
Http_ProcessRequest(&request);
|
||||
|
||||
Mutex_Lock(http_processedMutex);
|
||||
{
|
||||
Http_CompleteResult(&request);
|
||||
}
|
||||
Mutex_Unlock(http_processedMutex);
|
||||
|
||||
Mutex_Lock(http_curRequestMutex);
|
||||
{
|
||||
http_curRequest.ID[0] = '\0';
|
||||
http_curProgress = ASYNC_PROGRESS_NOTHING;
|
||||
}
|
||||
Mutex_Unlock(http_curRequestMutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-----------------------------------------------------Http component------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static void Http_Init(void) {
|
||||
ScheduledTask_Add(30, Http_PurgeOldEntriesTask);
|
||||
HttpRequestList_Init(&http_pending);
|
||||
HttpRequestList_Init(&http_processed);
|
||||
Http_SysInit();
|
||||
|
||||
http_waitable = Waitable_Create();
|
||||
http_pendingMutex = Mutex_Create();
|
||||
http_processedMutex = Mutex_Create();
|
||||
http_curRequestMutex = Mutex_Create();
|
||||
http_workerThread = Thread_Start(Http_WorkerLoop, false);
|
||||
}
|
||||
|
||||
static void Http_Free(void) {
|
||||
http_terminate = true;
|
||||
Http_ClearPending();
|
||||
Thread_Join(http_workerThread);
|
||||
|
||||
HttpRequestList_Free(&http_pending);
|
||||
HttpRequestList_Free(&http_processed);
|
||||
Http_SysFree();
|
||||
|
||||
Waitable_Free(http_waitable);
|
||||
Mutex_Free(http_pendingMutex);
|
||||
Mutex_Free(http_processedMutex);
|
||||
Mutex_Free(http_curRequestMutex);
|
||||
}
|
||||
|
||||
struct IGameComponent Http_Component = {
|
||||
Http_Init, /* Init */
|
||||
Http_Free, /* Free */
|
||||
Http_ClearPending /* Reset */
|
||||
};
|
70
src/Http.h
Normal file
70
src/Http.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef CC_HTTP_H
|
||||
#define CC_HTTP_H
|
||||
#include "Constants.h"
|
||||
#include "Utils.h"
|
||||
/* Aysnchronously performs http GET, HEAD, and POST requests.
|
||||
Typically this is used to download skins, texture packs, etc.
|
||||
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
*/
|
||||
struct IGameComponent;
|
||||
struct ScheduledTask;
|
||||
#define URL_MAX_SIZE (STRING_SIZE * 2)
|
||||
|
||||
extern struct IGameComponent Http_Component;
|
||||
/* TODO: Implement these */
|
||||
extern bool Http_UseCookies;
|
||||
/* TODO: Connection pooling */
|
||||
|
||||
enum HttpRequestType { REQUEST_TYPE_GET, REQUEST_TYPE_HEAD };
|
||||
enum HttpProgress {
|
||||
ASYNC_PROGRESS_NOTHING = -3,
|
||||
ASYNC_PROGRESS_MAKING_REQUEST = -2,
|
||||
ASYNC_PROGRESS_FETCHING_DATA = -1
|
||||
};
|
||||
|
||||
struct HttpRequest {
|
||||
char URL[URL_MAX_SIZE]; /* URL data is downloaded from/uploaded to. */
|
||||
char ID[STRING_SIZE]; /* Unique identifier for this request. */
|
||||
TimeMS TimeAdded; /* Time this request was added to queue of requests. */
|
||||
TimeMS TimeDownloaded; /* Time response contents were completely downloaded. */
|
||||
int StatusCode; /* HTTP status code returned in the response. */
|
||||
uint32_t ContentLength; /* HTTP content length returned in the response. */
|
||||
|
||||
ReturnCode Result; /* 0 on success, otherwise platform-specific error. */
|
||||
void* Data; /* Contents of the response. (i.e. result data) */
|
||||
uint32_t Size; /* Size of the contents. */
|
||||
|
||||
TimeMS LastModified; /* Time item cached at (if at all) */
|
||||
char Etag[STRING_SIZE]; /* ETag of cached item (if any) */
|
||||
uint8_t RequestType; /* Whether to fetch contents or just headers. */
|
||||
};
|
||||
|
||||
/* Frees data from a HTTP request. */
|
||||
void HttpRequest_Free(struct HttpRequest* request);
|
||||
|
||||
/* Aschronously performs a http GET request to download a skin. */
|
||||
/* If url is a skin, this is the same as Http_AsyncGetData. */
|
||||
/* If not, instead downloads from http://static.classicube.net/skins/[skinName].png */
|
||||
void Http_AsyncGetSkin(const String* id, const String* skinName);
|
||||
/* Asynchronously performs a http GET request. (e.g. to download data) */
|
||||
void Http_AsyncGetData(const String* url, bool priority, const String* id);
|
||||
/* Asynchronously performs a http HEAD request. (e.g. to get Content-Length header) */
|
||||
void Http_AsyncGetHeaders(const String* url, bool priority, const String* id);
|
||||
/* Asynchronously performs a http POST request. (e.g. to submit data) */
|
||||
/* NOTE: data and size MUST PERSIST until the request is completed. */
|
||||
/* TODO: maybe we shouldn't force that.. instead mem alloc data and size */
|
||||
void Http_AsyncPostData(const String* url, bool priority, const String* id, const void* data, const uint32_t size);
|
||||
/* Asynchronously performs a http GET request. (e.g. to download data) */
|
||||
/* Also sets the If-Modified-Since and If-None-Match headers. (if not NULL) */
|
||||
void Http_AsyncGetDataEx(const String* url, bool priority, const String* id, TimeMS* lastModified, const String* etag);
|
||||
|
||||
/* Attempts to retrieve a fully completed request. */
|
||||
/* NOTE: You MUST also check Result/StatusCode, and check Size is > 0. */
|
||||
/* (because a completed request may not have completed successfully) */
|
||||
bool Http_GetResult(const String* id, struct HttpRequest* item);
|
||||
/* Retrieves information about the request currently being processed. */
|
||||
bool Http_GetCurrent(struct HttpRequest* request, int* progress);
|
||||
/* Clears the list of pending requests. */
|
||||
void Http_ClearPending(void);
|
||||
void Http_PurgeOldEntriesTask(struct ScheduledTask* task);
|
||||
#endif
|
@ -949,7 +949,7 @@ static void ResourcesScreen_Download(void* w, int x, int y) {
|
||||
|
||||
static void ResourcesScreen_Next(void* w, int x, int y) {
|
||||
const static String optionsTxt = String_FromConst("options.txt");
|
||||
AsyncDownloader_Clear();
|
||||
Http_ClearPending();
|
||||
|
||||
if (File_Exists(&optionsTxt)) {
|
||||
Launcher_SetScreen(MainScreen_MakeInstance());
|
||||
@ -1036,7 +1036,7 @@ static void ResourcesScreen_SetStatus(const String* str) {
|
||||
LWidget_Draw(w);
|
||||
}
|
||||
|
||||
static void ResourcesScreen_UpdateStatus(struct AsyncRequest* req) {
|
||||
static void ResourcesScreen_UpdateStatus(struct HttpRequest* req) {
|
||||
String str; char strBuffer[STRING_SIZE];
|
||||
String id;
|
||||
BitmapCol boxCol = BITMAPCOL_CONST(120, 85, 151, 255);
|
||||
@ -1053,10 +1053,10 @@ static void ResourcesScreen_UpdateStatus(struct AsyncRequest* req) {
|
||||
}
|
||||
|
||||
static void ResourcesScreen_UpdateProgress(struct ResourcesScreen* s) {
|
||||
struct AsyncRequest req;
|
||||
struct HttpRequest req;
|
||||
int progress;
|
||||
|
||||
if (!AsyncDownloader_GetCurrent(&req, &progress)) return;
|
||||
if (!Http_GetCurrent(&req, &progress)) return;
|
||||
ResourcesScreen_UpdateStatus(&req);
|
||||
/* making request still, haven't started download yet */
|
||||
if (progress < 0 || progress > 100) return;
|
||||
@ -1386,9 +1386,9 @@ static void UpdatesScreen_CheckTick(struct UpdatesScreen* s) {
|
||||
static void UpdatesScreen_UpdateProgress(struct UpdatesScreen* s, struct LWebTask* task) {
|
||||
String str; char strBuffer[STRING_SIZE];
|
||||
String identifier;
|
||||
struct AsyncRequest item;
|
||||
struct HttpRequest item;
|
||||
int progress;
|
||||
if (!AsyncDownloader_GetCurrent(&item, &progress)) return;
|
||||
if (!Http_GetCurrent(&item, &progress)) return;
|
||||
|
||||
identifier = String_FromRawArray(item.ID);
|
||||
if (!String_Equals(&identifier, &task->Identifier)) return;
|
||||
|
@ -1,6 +1,6 @@
|
||||
#ifndef CC_LSCREENS_H
|
||||
#define CC_LSCREENS_H
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "String.h"
|
||||
#include "Input.h"
|
||||
/* Implements screens/menus for the launcher.
|
||||
|
18
src/LWeb.c
18
src/LWeb.c
@ -206,11 +206,11 @@ static void LWebTask_Reset(struct LWebTask* task) {
|
||||
}
|
||||
|
||||
void LWebTask_Tick(struct LWebTask* task) {
|
||||
struct AsyncRequest req;
|
||||
struct HttpRequest req;
|
||||
int delta;
|
||||
if (task->Completed) return;
|
||||
|
||||
if (!AsyncDownloader_Get(&task->Identifier, &req)) return;
|
||||
if (!Http_GetResult(&task->Identifier, &req)) return;
|
||||
delta = (int)(DateTime_CurrentUTC_MS() - task->Start);
|
||||
Platform_Log2("%s took %i", &task->Identifier, &delta);
|
||||
|
||||
@ -221,7 +221,7 @@ void LWebTask_Tick(struct LWebTask* task) {
|
||||
task->Completed = true;
|
||||
task->Success = !task->Res && req.Data && req.Size;
|
||||
if (task->Success) task->Handle(req.Data, req.Size);
|
||||
ASyncRequest_Free(&req);
|
||||
HttpRequest_Free(&req);
|
||||
}
|
||||
|
||||
|
||||
@ -249,7 +249,7 @@ void GetTokenTask_Run(void) {
|
||||
String_InitArray(GetTokenTask.Token, tokenBuffer);
|
||||
|
||||
GetTokenTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
GetTokenTask.Base.Handle = GetTokenTask_Handle;
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@ void SignInTask_Run(const String* user, const String* pass) {
|
||||
String_Copy(&SignInTask.Username, user);
|
||||
|
||||
SignInTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
SignInTask.Base.Handle = SignInTask_Handle;
|
||||
}
|
||||
// NOTE: Remember to add &c for errors here too
|
||||
@ -353,7 +353,7 @@ void FetchServerTask_Run(const String* hash) {
|
||||
String_Format1(&url, "https://www.classicube.net/api/server/%s", hash);
|
||||
|
||||
FetchServerTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
FetchServerTask.Base.Handle = FetchServerTask_Handle;
|
||||
}
|
||||
|
||||
@ -396,7 +396,7 @@ void FetchServersTask_Run(void) {
|
||||
FetchServersTask.NumServers = 0;
|
||||
|
||||
FetchServersTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
FetchServersTask.Base.Handle = FetchServersTask_Handle;
|
||||
}
|
||||
|
||||
@ -444,7 +444,7 @@ void CheckUpdateTask_Run(void) {
|
||||
String_InitArray(CheckUpdateTask.LatestRelease, relVersionBuffer);
|
||||
|
||||
CheckUpdateTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
CheckUpdateTask.Base.Handle = CheckUpdateTask_Handle;
|
||||
}
|
||||
|
||||
@ -494,7 +494,7 @@ void FetchUpdateTask_Run(bool release, bool d3d9) {
|
||||
FetchUpdateTask.Timestamp = release ? CheckUpdateTask.RelTimestamp : CheckUpdateTask.DevTimestamp;
|
||||
|
||||
FetchUpdateTask.Base.Identifier = id;
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
FetchUpdateTask.Base.Handle = FetchUpdateTask_Handle;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#ifndef CC_LWEB_H
|
||||
#define CC_LWEB_H
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "String.h"
|
||||
/* Implements asynchronous web tasks for the launcher.
|
||||
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "Window.h"
|
||||
#include "GameStructs.h"
|
||||
#include "Event.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "ExtMath.h"
|
||||
#include "Funcs.h"
|
||||
#include "Logger.h"
|
||||
@ -234,8 +234,8 @@ void Launcher_Run(void) {
|
||||
Launcher_Framebuffer.Height = Game_Height;
|
||||
Window_InitRaw(&Launcher_Framebuffer);
|
||||
|
||||
AsyncDownloader_Cookies = true;
|
||||
AsyncDownloader_Component.Init();
|
||||
Http_UseCookies = true;
|
||||
Http_Component.Init();
|
||||
|
||||
Resources_CheckExistence();
|
||||
CheckUpdateTask_Run();
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "ExtMath.h"
|
||||
#include "Window.h"
|
||||
#include "Camera.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Block.h"
|
||||
#include "World.h"
|
||||
#include "Formats.h"
|
||||
@ -3251,11 +3251,11 @@ static void TexPackOverlay_NoClick(void* screen, void* widget) {
|
||||
}
|
||||
|
||||
static void TexPackOverlay_Render(void* screen, double delta) {
|
||||
struct AsyncRequest item;
|
||||
struct HttpRequest item;
|
||||
struct TexPackOverlay* s = screen;
|
||||
|
||||
MenuScreen_Render(s, delta);
|
||||
if (!AsyncDownloader_Get(&s->Identifier, &item)) return;
|
||||
if (!Http_GetResult(&s->Identifier, &item)) return;
|
||||
|
||||
s->ContentLength = item.ContentLength;
|
||||
if (!s->ContentLength) return;
|
||||
@ -3326,7 +3326,7 @@ struct Screen* TexPackOverlay_MakeInstance(const String* url) {
|
||||
String_Format1(&s->Identifier, "CL_%s", url);
|
||||
s->ContentLength = 0;
|
||||
|
||||
AsyncDownloader_GetContentLength(url, true, &s->Identifier);
|
||||
Http_AsyncGetHeaders(url, true, &s->Identifier);
|
||||
s->VTABLE = &TexPackOverlay_VTABLE;
|
||||
return (struct Screen*)s;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "Model.h"
|
||||
#include "Funcs.h"
|
||||
#include "Lighting.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Drawer2D.h"
|
||||
#include "Logger.h"
|
||||
#include "TexturePack.h"
|
||||
@ -243,7 +243,7 @@ static void WoM_CheckMotd(void) {
|
||||
applied in the new world if the async 'get env request' didn't complete before the old world was unloaded */
|
||||
wom_counter++;
|
||||
WoM_UpdateIdentifier();
|
||||
AsyncDownloader_GetData(&url, true, &wom_identifier);
|
||||
Http_AsyncGetData(&url, true, &wom_identifier);
|
||||
wom_sendId = true;
|
||||
}
|
||||
|
||||
@ -268,7 +268,7 @@ static PackedCol WoM_ParseCol(const String* value, PackedCol defaultCol) {
|
||||
return col;
|
||||
}
|
||||
|
||||
static void WoM_ParseConfig(struct AsyncRequest* item) {
|
||||
static void WoM_ParseConfig(struct HttpRequest* item) {
|
||||
String line; char lineBuffer[STRING_SIZE * 2];
|
||||
struct Stream mem;
|
||||
String key, value;
|
||||
@ -308,11 +308,11 @@ static void WoM_Reset(void) {
|
||||
}
|
||||
|
||||
static void WoM_Tick(void) {
|
||||
struct AsyncRequest item;
|
||||
if (!AsyncDownloader_Get(&wom_identifier, &item)) return;
|
||||
struct HttpRequest item;
|
||||
if (!Http_GetResult(&wom_identifier, &item)) return;
|
||||
|
||||
if (item.Data) WoM_ParseConfig(&item);
|
||||
ASyncRequest_Free(&item);
|
||||
HttpRequest_Free(&item);
|
||||
}
|
||||
|
||||
|
||||
|
272
src/Platform.c
272
src/Platform.c
@ -4,7 +4,7 @@
|
||||
#include "ExtMath.h"
|
||||
#include "Drawer2D.h"
|
||||
#include "Funcs.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Bitmap.h"
|
||||
#include "Window.h"
|
||||
|
||||
@ -1320,276 +1320,6 @@ ReturnCode Socket_Poll(SocketHandle socket, int mode, bool* success) {
|
||||
#endif
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*----------------------------------------------------------Http-----------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
#ifdef CC_BUILD_WIN
|
||||
static HINTERNET hInternet;
|
||||
/* TODO: Test last modified and etag even work */
|
||||
#define FLAG_STATUS HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER
|
||||
#define FLAG_LENGTH HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER
|
||||
#define FLAG_LASTMOD HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME
|
||||
|
||||
void Http_Init(void) {
|
||||
/* TODO: Should we use INTERNET_OPEN_TYPE_PRECONFIG instead? */
|
||||
hInternet = InternetOpenA(GAME_APP_NAME, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
||||
if (!hInternet) Logger_Abort2(GetLastError(), "Failed to init WinINet");
|
||||
}
|
||||
|
||||
static ReturnCode Http_Make(struct AsyncRequest* req, HINTERNET* handle) {
|
||||
String url = String_FromRawArray(req->URL);
|
||||
char urlStr[URL_MAX_SIZE + 1];
|
||||
Mem_Copy(urlStr, url.buffer, url.length);
|
||||
|
||||
urlStr[url.length] = '\0';
|
||||
char headersBuffer[STRING_SIZE * 3];
|
||||
String headers = String_FromArray(headersBuffer);
|
||||
|
||||
/* https://stackoverflow.com/questions/25308488/c-wininet-custom-http-headers */
|
||||
if (req->Etag[0] || req->LastModified) {
|
||||
if (req->LastModified) {
|
||||
String_AppendConst(&headers, "If-Modified-Since: ");
|
||||
DateTime_HttpDate(req->LastModified, &headers);
|
||||
String_AppendConst(&headers, "\r\n");
|
||||
}
|
||||
|
||||
if (req->Etag[0]) {
|
||||
String etag = String_FromRawArray(req->Etag);
|
||||
String_AppendConst(&headers, "If-None-Match: ");
|
||||
String_AppendString(&headers, &etag);
|
||||
String_AppendConst(&headers, "\r\n");
|
||||
}
|
||||
|
||||
String_AppendConst(&headers, "\r\n\r\n");
|
||||
headers.buffer[headers.length] = '\0';
|
||||
} else { headers.buffer = NULL; }
|
||||
|
||||
*handle = InternetOpenUrlA(hInternet, urlStr, headers.buffer, headers.length,
|
||||
INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD, 0);
|
||||
return *handle ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
static ReturnCode Http_GetHeaders(struct AsyncRequest* req, HINTERNET handle) {
|
||||
DWORD len;
|
||||
|
||||
len = sizeof(DWORD);
|
||||
if (!HttpQueryInfoA(handle, FLAG_STATUS, &req->StatusCode, &len, NULL)) return GetLastError();
|
||||
|
||||
len = sizeof(DWORD);
|
||||
HttpQueryInfoA(handle, FLAG_LENGTH, &req->ContentLength, &len, NULL);
|
||||
|
||||
SYSTEMTIME sysTime;
|
||||
len = sizeof(SYSTEMTIME);
|
||||
if (HttpQueryInfoA(handle, FLAG_LASTMOD, &sysTime, &len, NULL)) {
|
||||
struct DateTime time;
|
||||
Platform_FromSysTime(&time, &sysTime);
|
||||
req->LastModified = DateTime_TotalMs(&time);
|
||||
}
|
||||
|
||||
String etag = String_ClearedArray(req->Etag);
|
||||
len = etag.capacity;
|
||||
HttpQueryInfoA(handle, HTTP_QUERY_ETAG, etag.buffer, &len, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ReturnCode Http_GetData(struct AsyncRequest* req, HINTERNET handle, volatile int* progress) {
|
||||
uint8_t* buffer;
|
||||
uint32_t size, totalRead;
|
||||
uint32_t read, avail;
|
||||
bool success;
|
||||
|
||||
*progress = 0;
|
||||
size = req->ContentLength ? req->ContentLength : 1;
|
||||
buffer = Mem_Alloc(size, 1, "http get data");
|
||||
totalRead = 0;
|
||||
|
||||
req->Data = buffer;
|
||||
req->Size = 0;
|
||||
|
||||
for (;;) {
|
||||
if (!InternetQueryDataAvailable(handle, &avail, 0, 0)) break;
|
||||
if (!avail) break;
|
||||
|
||||
/* expand if buffer is too small (some servers don't give content length) */
|
||||
if (totalRead + avail > size) {
|
||||
size = totalRead + avail;
|
||||
buffer = Mem_Realloc(buffer, size, 1, "http inc data");
|
||||
req->Data = buffer;
|
||||
}
|
||||
|
||||
success = InternetReadFile(handle, &buffer[totalRead], avail, &read);
|
||||
if (!success) { Mem_Free(buffer); return GetLastError(); }
|
||||
if (!read) break;
|
||||
|
||||
totalRead += read;
|
||||
if (req->ContentLength) *progress = (int)(100.0f * totalRead / size);
|
||||
req->Size += read;
|
||||
}
|
||||
|
||||
*progress = 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReturnCode Http_Do(struct AsyncRequest* req, volatile int* progress) {
|
||||
HINTERNET handle;
|
||||
ReturnCode res = Http_Make(req, &handle);
|
||||
if (res) return res;
|
||||
|
||||
*progress = ASYNC_PROGRESS_FETCHING_DATA;
|
||||
res = Http_GetHeaders(req, handle);
|
||||
if (res) { InternetCloseHandle(handle); return res; }
|
||||
|
||||
if (req->RequestType != REQUEST_TYPE_CONTENT_LENGTH && req->StatusCode == 200) {
|
||||
res = Http_GetData(req, handle, progress);
|
||||
if (res) { InternetCloseHandle(handle); return res; }
|
||||
}
|
||||
|
||||
return InternetCloseHandle(handle) ? 0 : GetLastError();
|
||||
}
|
||||
|
||||
ReturnCode Http_Free(void) {
|
||||
return InternetCloseHandle(hInternet) ? 0 : GetLastError();
|
||||
}
|
||||
#endif
|
||||
#ifdef CC_BUILD_POSIX
|
||||
CURL* curl;
|
||||
|
||||
void Http_Init(void) {
|
||||
CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
if (res) Logger_Abort2(res, "Failed to init curl");
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) Logger_Abort("Failed to init easy curl");
|
||||
}
|
||||
|
||||
static int Http_Progress(int* progress, double total, double received, double a, double b) {
|
||||
if (total == 0) return 0;
|
||||
*progress = (int)(100 * received / total);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct curl_slist* Http_Make(struct AsyncRequest* req) {
|
||||
String tmp; char buffer[STRING_SIZE + 1];
|
||||
struct curl_slist* list = NULL;
|
||||
String etag;
|
||||
|
||||
if (req->Etag[0]) {
|
||||
String_InitArray_NT(tmp, buffer);
|
||||
String_AppendConst(&tmp, "If-None-Match: ");
|
||||
|
||||
etag = String_FromRawArray(req->Etag);
|
||||
String_AppendString(&tmp, &etag);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
list = curl_slist_append(list, tmp.buffer);
|
||||
}
|
||||
|
||||
if (req->LastModified) {
|
||||
String_InitArray_NT(tmp, buffer);
|
||||
String_AppendConst(&tmp, "Last-Modified: ");
|
||||
|
||||
DateTime_HttpDate(req->LastModified, &tmp);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
list = curl_slist_append(list, tmp.buffer);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static size_t Http_GetHeaders(char *buffer, size_t size, size_t nitems, struct AsyncRequest* req) {
|
||||
String tmp; char tmpBuffer[STRING_SIZE + 1];
|
||||
String line, name, value;
|
||||
time_t time;
|
||||
|
||||
if (size != 1) return size * nitems; /* non byte header */
|
||||
line = String_Init(buffer, nitems, nitems);
|
||||
if (!String_UNSAFE_Separate(&line, ':', &name, &value)) return nitems;
|
||||
|
||||
/* value usually has \r\n at end */
|
||||
if (value.length && value.buffer[value.length - 1] == '\n') value.length--;
|
||||
if (value.length && value.buffer[value.length - 1] == '\r') value.length--;
|
||||
if (!value.length) return nitems;
|
||||
|
||||
if (String_CaselessEqualsConst(&name, "ETag")) {
|
||||
tmp = String_ClearedArray(req->Etag);
|
||||
String_AppendString(&tmp, &value);
|
||||
} else if (String_CaselessEqualsConst(&name, "Content-Length")) {
|
||||
Convert_ParseInt(&value, &req->ContentLength);
|
||||
/* TODO: Fix when ContentLength isn't RequestSize */
|
||||
req->Size = req->ContentLength;
|
||||
} else if (String_CaselessEqualsConst(&name, "Last-Modified")) {
|
||||
String_InitArray_NT(tmp, tmpBuffer);
|
||||
String_AppendString(&tmp, &value);
|
||||
tmp.buffer[tmp.length] = '\0';
|
||||
|
||||
time = curl_getdate(tmp.buffer, NULL);
|
||||
if (time == -1) return nitems;
|
||||
req->LastModified = (uint64_t)time * 1000 + UNIX_EPOCH;
|
||||
}
|
||||
return nitems;
|
||||
}
|
||||
|
||||
static size_t Http_GetData(char *buffer, size_t size, size_t nitems, struct AsyncRequest* req) {
|
||||
uint32_t total, left;
|
||||
uint8_t* dst;
|
||||
|
||||
total = req->Size;
|
||||
if (!total || req->RequestType == REQUEST_TYPE_CONTENT_LENGTH) return 0;
|
||||
if (!req->Data) req->Data = Mem_Alloc(total, 1, "http get data");
|
||||
|
||||
/* reuse Result as an offset */
|
||||
left = total - req->Result;
|
||||
left = min(left, nitems);
|
||||
dst = (uint8_t*)req->Data + req->Result;
|
||||
|
||||
Mem_Copy(dst, buffer, left);
|
||||
req->Result += left;
|
||||
return nitems;
|
||||
}
|
||||
|
||||
ReturnCode Http_Do(struct AsyncRequest* req, volatile int* progress) {
|
||||
struct curl_slist* list;
|
||||
String url = String_FromRawArray(req->URL);
|
||||
char urlStr[600];
|
||||
long status = 0;
|
||||
CURLcode res;
|
||||
|
||||
Platform_ConvertString(urlStr, &url);
|
||||
curl_easy_reset(curl);
|
||||
list = Http_Make(req);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, urlStr);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, GAME_APP_NAME);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, Http_Progress);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, progress);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, Http_GetHeaders);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, req);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Http_GetData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
|
||||
|
||||
*progress = ASYNC_PROGRESS_FETCHING_DATA;
|
||||
res = curl_easy_perform(curl);
|
||||
*progress = 100;
|
||||
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
req->StatusCode = status;
|
||||
|
||||
curl_slist_free_all(list);
|
||||
return res;
|
||||
}
|
||||
|
||||
ReturnCode Http_Free(void) {
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*----------------------------------------------------------Audio----------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
|
@ -6,7 +6,7 @@
|
||||
Copyright 2017 ClassicalSharp | Licensed under BSD-3
|
||||
*/
|
||||
struct DrawTextArgs;
|
||||
struct AsyncRequest;
|
||||
struct HttpRequest;
|
||||
struct DateTime;
|
||||
|
||||
enum Socket_PollMode { SOCKET_POLL_READ, SOCKET_POLL_WRITE };
|
||||
@ -221,14 +221,6 @@ ReturnCode Socket_Close(SocketHandle socket);
|
||||
/* NOTE: A socket is considered writable once it has finished connecting. */
|
||||
ReturnCode Socket_Poll(SocketHandle socket, int mode, bool* success);
|
||||
|
||||
/* Initalises the platform specific http library state. */
|
||||
void Http_Init(void);
|
||||
/* Performs a http request, setting progress as data is received. */
|
||||
/* NOTE: NOT thread safe - you should ALWAYS use AsyncDownloader for making requests. */
|
||||
ReturnCode Http_Do(struct AsyncRequest* req, volatile int* progress);
|
||||
/* Frees the platform specific http library state. */
|
||||
ReturnCode Http_Free(void);
|
||||
|
||||
#define AUDIO_MAX_BUFFERS 4
|
||||
/* Information about a source of audio. */
|
||||
struct AudioFormat { uint16_t Channels, BitsPerSample; int SampleRate; };
|
||||
|
@ -87,9 +87,9 @@ int main(int argc, char** argv) {
|
||||
|
||||
argsCount = Platform_GetCommandLineArgs(argc, argv, args);
|
||||
/* NOTE: Make sure to comment this out before pushing a commit */
|
||||
/* String rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565"); */
|
||||
String rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565");
|
||||
/* String rawArgs = String_FromConst("UnknownShadow200"); */
|
||||
/* argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4); */
|
||||
argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4);
|
||||
|
||||
if (argsCount == 0) {
|
||||
Launcher_Run();
|
||||
|
@ -651,7 +651,7 @@ static void SoundPatcher_DecodeAudio(struct Stream* s, struct VorbisState* ctx)
|
||||
Mem_Free(samples);
|
||||
}
|
||||
|
||||
static void SoundPatcher_Save(struct ResourceSound* sound, struct AsyncRequest* req) {
|
||||
static void SoundPatcher_Save(struct ResourceSound* sound, struct HttpRequest* req) {
|
||||
String path; char pathBuffer[STRING_SIZE];
|
||||
uint8_t buffer[OGG_BUFFER_SIZE];
|
||||
struct Stream src, ogg, dst;
|
||||
@ -675,7 +675,7 @@ static void SoundPatcher_Save(struct ResourceSound* sound, struct AsyncRequest*
|
||||
if (res) Logger_Warn(res, "closing .wav file");
|
||||
}
|
||||
|
||||
static void MusicPatcher_Save(struct ResourceMusic* music, struct AsyncRequest* req) {
|
||||
static void MusicPatcher_Save(struct ResourceMusic* music, struct HttpRequest* req) {
|
||||
String path; char pathBuffer[STRING_SIZE];
|
||||
ReturnCode res;
|
||||
|
||||
@ -701,7 +701,7 @@ CC_NOINLINE static void Fetcher_DownloadAudio(const char* name, const char* hash
|
||||
String_InitArray(url, urlBuffer);
|
||||
String_Format3(&url, "http://resources.download.minecraft.net/%r%r/%c",
|
||||
&hash[0], &hash[1], hash);
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
}
|
||||
|
||||
void Fetcher_Run(void) {
|
||||
@ -721,7 +721,7 @@ void Fetcher_Run(void) {
|
||||
|
||||
id = String_FromReadonly(Resources_Files[i].Name);
|
||||
url = String_FromReadonly(Resources_Files[i].Url);
|
||||
AsyncDownloader_GetData(&url, false, &id);
|
||||
Http_AsyncGetData(&url, false, &id);
|
||||
}
|
||||
|
||||
for (i = 0; i < Array_Elems(Resources_Music); i++) {
|
||||
@ -739,8 +739,8 @@ static void Fetcher_Finish(void) {
|
||||
Fetcher_Working = false;
|
||||
}
|
||||
|
||||
CC_NOINLINE static bool Fetcher_Get(const String* id, struct AsyncRequest* req) {
|
||||
if (!AsyncDownloader_Get(id, req)) return false;
|
||||
CC_NOINLINE static bool Fetcher_Get(const String* id, struct HttpRequest* req) {
|
||||
if (!Http_GetResult(id, req)) return false;
|
||||
|
||||
if (req->Result) {
|
||||
Fetcher_Error = req->Result;
|
||||
@ -762,7 +762,7 @@ CC_NOINLINE static bool Fetcher_Get(const String* id, struct AsyncRequest* req)
|
||||
|
||||
static void Fetcher_CheckFile(struct ResourceFile* file) {
|
||||
String id = String_FromReadonly(file->Name);
|
||||
struct AsyncRequest req;
|
||||
struct HttpRequest req;
|
||||
if (!Fetcher_Get(&id, &req)) return;
|
||||
|
||||
file->Downloaded = true;
|
||||
@ -773,21 +773,21 @@ static void Fetcher_CheckFile(struct ResourceFile* file) {
|
||||
|
||||
static void Fetcher_CheckMusic(struct ResourceMusic* music) {
|
||||
String id = String_FromReadonly(music->Name);
|
||||
struct AsyncRequest req;
|
||||
struct HttpRequest req;
|
||||
if (!Fetcher_Get(&id, &req)) return;
|
||||
|
||||
music->Downloaded = true;
|
||||
MusicPatcher_Save(music, &req);
|
||||
ASyncRequest_Free(&req);
|
||||
HttpRequest_Free(&req);
|
||||
}
|
||||
|
||||
static void Fetcher_CheckSound(struct ResourceSound* sound) {
|
||||
String id = String_FromReadonly(sound->Name);
|
||||
struct AsyncRequest req;
|
||||
struct HttpRequest req;
|
||||
if (!Fetcher_Get(&id, &req)) return;
|
||||
|
||||
SoundPatcher_Save(sound, &req);
|
||||
ASyncRequest_Free(&req);
|
||||
HttpRequest_Free(&req);
|
||||
}
|
||||
|
||||
/* TODO: Implement this.. */
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "ExtMath.h"
|
||||
#include "Window.h"
|
||||
#include "Camera.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Block.h"
|
||||
#include "Menus.h"
|
||||
#include "World.h"
|
||||
@ -816,12 +816,12 @@ static void ChatScreen_SetInitialMessages(struct ChatScreen* s) {
|
||||
static void ChatScreen_CheckOtherStatuses(struct ChatScreen* s) {
|
||||
const static String texPack = String_FromConst("texturePack");
|
||||
String str; char strBuffer[STRING_SIZE];
|
||||
struct AsyncRequest request;
|
||||
struct HttpRequest request;
|
||||
int progress;
|
||||
bool hasRequest;
|
||||
String identifier;
|
||||
|
||||
hasRequest = AsyncDownloader_GetCurrent(&request, &progress);
|
||||
hasRequest = Http_GetCurrent(&request, &progress);
|
||||
identifier = String_FromRawArray(request.ID);
|
||||
|
||||
/* Is terrain / texture pack currently being downloaded? */
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "Chat.h"
|
||||
#include "Block.h"
|
||||
#include "Event.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Funcs.h"
|
||||
#include "Entity.h"
|
||||
#include "Graphics.h"
|
||||
@ -74,13 +74,13 @@ void ServerConnection_DownloadTexturePack(const String* url) {
|
||||
}
|
||||
|
||||
TexturePack_ExtractCurrent(url);
|
||||
AsyncDownloader_GetDataEx(url, true, &texPack, &lastModified, &etag);
|
||||
Http_AsyncGetDataEx(url, true, &texPack, &lastModified, &etag);
|
||||
}
|
||||
|
||||
void ServerConnection_CheckAsyncResources(void) {
|
||||
const static String texPack = String_FromConst("texturePack");
|
||||
struct AsyncRequest item;
|
||||
if (!AsyncDownloader_Get(&texPack, &item)) return;
|
||||
struct HttpRequest item;
|
||||
if (!Http_GetResult(&texPack, &item)) return;
|
||||
|
||||
if (item.Data) {
|
||||
TexturePack_Extract_Req(&item);
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "Graphics.h"
|
||||
#include "Event.h"
|
||||
#include "Game.h"
|
||||
#include "AsyncDownloader.h"
|
||||
#include "Http.h"
|
||||
#include "Platform.h"
|
||||
#include "Deflate.h"
|
||||
#include "Stream.h"
|
||||
@ -706,7 +706,7 @@ void TexturePack_ExtractCurrent(const String* url) {
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePack_Extract_Req(struct AsyncRequest* item) {
|
||||
void TexturePack_Extract_Req(struct HttpRequest* item) {
|
||||
String url, etag;
|
||||
void* data; uint32_t len;
|
||||
struct Stream mem;
|
||||
@ -729,5 +729,5 @@ void TexturePack_Extract_Req(struct AsyncRequest* item) {
|
||||
: TexturePack_ExtractZip(&mem);
|
||||
|
||||
if (res) Logger_Warn2(res, png ? "decoding" : "extracting", &url);
|
||||
ASyncRequest_Free(item);
|
||||
HttpRequest_Free(item);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 */
|
||||
|
||||
struct Stream;
|
||||
struct AsyncRequest;
|
||||
struct HttpRequest;
|
||||
struct IGameComponent;
|
||||
extern struct IGameComponent Animations_Component;
|
||||
|
||||
@ -97,5 +97,5 @@ void TextureCache_SetLastModified(const String* url, const TimeMS* lastModified)
|
||||
void TexturePack_ExtractZip_File(const String* filename);
|
||||
void TexturePack_ExtractDefault(void);
|
||||
void TexturePack_ExtractCurrent(const String* url);
|
||||
void TexturePack_Extract_Req(struct AsyncRequest* item);
|
||||
void TexturePack_Extract_Req(struct HttpRequest* item);
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user