summary refs log tree commit diff
path: root/src/Http_Web.c
blob: 17da0a1daf89ce1409ef41d3d9ab731456b6548a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include "Core.h"
#ifdef CC_BUILD_WEB
#include "_HttpBase.h"
#include <emscripten/emscripten.h>
#include "Errors.h"
extern int interop_DownloadAsync(const char* url, int method, int reqID);
extern int interop_IsHttpsOnly(void);
static struct RequestList workingReqs, queuedReqs;
static cc_uint64 startTime;


/*########################################################################################################################*
*----------------------------------------------------Http public api------------------------------------------------------*
*#########################################################################################################################*/
cc_bool Http_GetResult(int reqID, struct HttpRequest* item) {
	int i = RequestList_Find(&processedReqs, reqID);

	if (i >= 0) *item = processedReqs.entries[i];
	if (i >= 0) RequestList_RemoveAt(&processedReqs, i);
	return i >= 0;
}

cc_bool Http_GetCurrent(int* reqID, int* progress) {
	/* TODO: Stubbed as this isn't required at the moment */
	*progress = 0;
	return 0;
}

int Http_CheckProgress(int reqID) {
	int idx = RequestList_Find(&workingReqs, reqID);
	if (idx == -1) return HTTP_PROGRESS_NOT_WORKING_ON;

	return workingReqs.entries[idx].progress;
}

void Http_ClearPending(void) {
	RequestList_Free(&queuedReqs);
	RequestList_Free(&workingReqs);
}

void Http_TryCancel(int reqID) {
	RequestList_TryFree(&queuedReqs,    reqID);
	RequestList_TryFree(&workingReqs,   reqID);
	RequestList_TryFree(&processedReqs, reqID);
}


/*########################################################################################################################*
*----------------------------------------------------Emscripten backend---------------------------------------------------*
*#########################################################################################################################*/
static cc_bool HttpBackend_DescribeError(cc_result res, cc_string* dst) { 
	return false; 
}

#define HTTP_MAX_CONCURRENCY 6
static void Http_StartNextDownload(void) {
	char urlBuffer[URL_MAX_SIZE]; cc_string url;
	char urlStr[NATIVE_STR_LEN];
	struct HttpRequest* req;
	cc_result res;

	/* Avoid making too many requests at once */
	if (workingReqs.count >= HTTP_MAX_CONCURRENCY) return;
	if (!queuedReqs.count) return;
	String_InitArray(url, urlBuffer);

	req = &queuedReqs.entries[0];
	Http_GetUrl(req, &url);
	Platform_Log1("Fetching %s", &url);

	String_EncodeUtf8(urlStr, &url);
	res = interop_DownloadAsync(urlStr, req->requestType, req->id);

	if (res) {
		/* interop error code -> ClassiCube error code */
		if (res == 1) res = ERR_INVALID_DATA_URL;
		req->result = res;
		
		/* Invalid URL so move onto next request */
		Http_FinishRequest(req);
		RequestList_RemoveAt(&queuedReqs, 0);
		Http_StartNextDownload();
	} else {
		RequestList_Append(&workingReqs, req, false);
		RequestList_RemoveAt(&queuedReqs, 0);
	}
}

EMSCRIPTEN_KEEPALIVE void Http_OnUpdateProgress(int reqID, int read, int total) {
	int idx = RequestList_Find(&workingReqs, reqID);
	if (idx == -1 || !total) return;

	workingReqs.entries[idx].progress = (int)(100.0f * read / total);
}

EMSCRIPTEN_KEEPALIVE void Http_OnFinishedAsync(int reqID, void* data, int len, int status) {
	struct HttpRequest* req;
	int idx = RequestList_Find(&workingReqs, reqID);

	if (idx == -1) {
		/* Shouldn't ever happen, but log a warning anyways */
		Mem_Free(data); 
		Platform_Log1("Ignoring invalid request (%i)", &reqID);
	} else {
		req = &workingReqs.entries[idx];
		req->data          = data;
		req->size          = len;
		req->statusCode    = status;
		req->contentLength = len;

		/* Usually this happens when denied by CORS */
		if (!status && !data) req->result = ERR_DOWNLOAD_INVALID;

		if (req->data) Platform_Log1("HTTP returned data: %i bytes", &req->size);
		Http_FinishRequest(req);
		RequestList_RemoveAt(&workingReqs, idx);
	}
	Http_StartNextDownload();
}

/* Adds a req to the list of pending requests, waking up worker thread if needed */
static void HttpBackend_Add(struct HttpRequest* req, cc_uint8 flags) {
	/* Add time based query string parameter to bypass browser cache */
	if (flags & HTTP_FLAG_NOCACHE) {
		cc_string url = String_FromRawArray(req->url);
		int lo = (int)(startTime), hi = (int)(startTime >> 32);
		String_Format2(&url, "?t=%i%i", &hi, &lo);
	}

	RequestList_Append(&queuedReqs, req, flags);
	Http_StartNextDownload();
}


/*########################################################################################################################*
*-----------------------------------------------------Http component------------------------------------------------------*
*#########################################################################################################################*/
static void Http_Init(void) {
	Http_InitCommon();
	/* If this webpage is https://, browsers deny any http:// downloading */
	httpsOnly = interop_IsHttpsOnly();
	startTime = DateTime_CurrentUTC();

	RequestList_Init(&queuedReqs);
	RequestList_Init(&workingReqs);
	RequestList_Init(&processedReqs);
}
#endif