diff --git a/ClassicalSharp/Utils/Camera.cs b/ClassicalSharp/Utils/Camera.cs index 19f929bb5..22396c58b 100644 --- a/ClassicalSharp/Utils/Camera.cs +++ b/ClassicalSharp/Utils/Camera.cs @@ -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; diff --git a/src/AsyncDownloader.c b/src/AsyncDownloader.c deleted file mode 100644 index 77a430cca..000000000 --- a/src/AsyncDownloader.c +++ /dev/null @@ -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 */ -}; diff --git a/src/AsyncDownloader.h b/src/AsyncDownloader.h deleted file mode 100644 index 8b8226bca..000000000 --- a/src/AsyncDownloader.h +++ /dev/null @@ -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 diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index 6f9aac369..5be88b25e 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -187,7 +187,7 @@ - + @@ -253,7 +253,7 @@ - + diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index a2b9f04c2..d6f15809c 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -234,9 +234,6 @@ Header Files\Network - - Header Files\Network - Header Files\Game @@ -318,6 +315,9 @@ Header Files\Platform + + Header Files\Network + @@ -440,9 +440,6 @@ Source Files\Network - - Source Files\Network - Source Files\Rendering @@ -566,5 +563,8 @@ Source Files\Platform + + Source Files\Network + \ No newline at end of file diff --git a/src/Entity.c b/src/Entity.c index 7adecea58..95cb5b4db 100644 --- a/src/Entity.c +++ b/src/Entity.c @@ -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); diff --git a/src/Game.c b/src/Game.c index 9329e4ee3..84eb033b3 100644 --- a/src/Game.c +++ b/src/Game.c @@ -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); diff --git a/src/Http.c b/src/Http.c new file mode 100644 index 000000000..6fec004f3 --- /dev/null +++ b/src/Http.c @@ -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 +#include + +#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 +#include +#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 */ +}; diff --git a/src/Http.h b/src/Http.h new file mode 100644 index 000000000..7fbfd6a17 --- /dev/null +++ b/src/Http.h @@ -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 diff --git a/src/LScreens.c b/src/LScreens.c index 090ba3e58..83f3080ee 100644 --- a/src/LScreens.c +++ b/src/LScreens.c @@ -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; diff --git a/src/LScreens.h b/src/LScreens.h index 5da62ee46..2ca626fd9 100644 --- a/src/LScreens.h +++ b/src/LScreens.h @@ -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. diff --git a/src/LWeb.c b/src/LWeb.c index 8065c50c7..aeb0c9fcb 100644 --- a/src/LWeb.c +++ b/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; } diff --git a/src/LWeb.h b/src/LWeb.h index d80d9787e..da8efee25 100644 --- a/src/LWeb.h +++ b/src/LWeb.h @@ -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 diff --git a/src/Launcher.c b/src/Launcher.c index b9200bd9e..213fb4e91 100644 --- a/src/Launcher.c +++ b/src/Launcher.c @@ -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(); diff --git a/src/Menus.c b/src/Menus.c index 1889b0a99..2c54a86d4 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -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; } diff --git a/src/PacketHandlers.c b/src/PacketHandlers.c index 10f442459..8de11e6e8 100644 --- a/src/PacketHandlers.c +++ b/src/PacketHandlers.c @@ -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); } diff --git a/src/Platform.c b/src/Platform.c index ee9ebcfa1..58cc4e1da 100644 --- a/src/Platform.c +++ b/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----------------------------------------------------------* *#########################################################################################################################*/ diff --git a/src/Platform.h b/src/Platform.h index 1ea928797..ff0606cc3 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -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; }; diff --git a/src/Program.c b/src/Program.c index 3e21e38b9..130291102 100644 --- a/src/Program.c +++ b/src/Program.c @@ -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(); diff --git a/src/Resources.c b/src/Resources.c index 87431d18c..246a87748 100644 --- a/src/Resources.c +++ b/src/Resources.c @@ -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.. */ diff --git a/src/Screens.c b/src/Screens.c index 7c665a487..0b9caf321 100644 --- a/src/Screens.c +++ b/src/Screens.c @@ -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? */ diff --git a/src/ServerConnection.c b/src/ServerConnection.c index e261a3ed8..2765b2155 100644 --- a/src/ServerConnection.c +++ b/src/ServerConnection.c @@ -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); diff --git a/src/TexturePack.c b/src/TexturePack.c index 27fa47c42..557486469 100644 --- a/src/TexturePack.c +++ b/src/TexturePack.c @@ -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); } diff --git a/src/TexturePack.h b/src/TexturePack.h index 475fea231..e8bcbc2d8 100644 --- a/src/TexturePack.h +++ b/src/TexturePack.h @@ -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