Initial commit
This commit is contained in:
113
backends/networking/http/android/connectionmanager-android.cpp
Normal file
113
backends/networking/http/android/connectionmanager-android.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Allow use of stuff in <time.h> and abort()
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
|
||||
|
||||
// Disable printf override in common/forbidden.h to avoid
|
||||
// clashes with log.h from the Android SDK.
|
||||
// That header file uses
|
||||
// __attribute__ ((format(printf, 3, 4)))
|
||||
// which gets messed up by our override mechanism; this could
|
||||
// be avoided by either changing the Android SDK to use the equally
|
||||
// legal and valid
|
||||
// __attribute__ ((format(__printf__, 3, 4)))
|
||||
// or by refining our printf override to use a varadic macro
|
||||
// (which then wouldn't be portable, though).
|
||||
// Anyway, for now we just disable the printf override globally
|
||||
// for the Android port
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
|
||||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
#include "backends/networking/basic/android/jni.h"
|
||||
|
||||
#include "backends/networking/http/android/connectionmanager-android.h"
|
||||
#include "backends/networking/http/android/networkreadstream-android.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template<>
|
||||
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
|
||||
return new Networking::ConnectionManagerAndroid();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace Networking {
|
||||
|
||||
ConnectionManagerAndroid::ConnectionManagerAndroid() : ConnectionManager(), _manager(0) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
NetJNI::init(env);
|
||||
|
||||
// As we are called once, don't bother storing a global reference in JNI init
|
||||
jclass cls = env->FindClass("org/scummvm/scummvm/net/HTTPManager");
|
||||
jobject obj = env->NewObject(cls, NetJNI::_MID_manager_init);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPManager::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
abort();
|
||||
}
|
||||
_manager = env->NewGlobalRef(obj);
|
||||
env->DeleteLocalRef(obj);
|
||||
}
|
||||
|
||||
ConnectionManagerAndroid::~ConnectionManagerAndroid() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
env->DeleteGlobalRef(_manager);
|
||||
}
|
||||
|
||||
void ConnectionManagerAndroid::registerRequest(JNIEnv *env, jobject request) const {
|
||||
env->CallVoidMethod(_manager, NetJNI::_MID_manager_startRequest, request);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPManager::startRequest failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
// private goes here:
|
||||
void ConnectionManagerAndroid::processTransfers() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
// Don't call Java is there is no need
|
||||
// This is not perfect as the worker threads can update it and we are not
|
||||
// synchronized but that should do the job
|
||||
if (env->GetBooleanField(_manager, NetJNI::_FID_manager__empty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(_manager, NetJNI::_MID_manager_poll);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPManager::poll failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
46
backends/networking/http/android/connectionmanager-android.h
Normal file
46
backends/networking/http/android/connectionmanager-android.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
|
||||
#define BACKENDS_NETWORKING_HTTP_ANDROID_CONNECTIONMANAGER_ANDROID_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ConnectionManagerAndroid : public ConnectionManager {
|
||||
private:
|
||||
jobject _manager;
|
||||
|
||||
void processTransfers() override;
|
||||
|
||||
public:
|
||||
ConnectionManagerAndroid();
|
||||
~ConnectionManagerAndroid() override;
|
||||
|
||||
void registerRequest(JNIEnv *env, jobject request) const;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
408
backends/networking/http/android/networkreadstream-android.cpp
Normal file
408
backends/networking/http/android/networkreadstream-android.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Allow use of stuff in <time.h> and abort()
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
|
||||
|
||||
// Disable printf override in common/forbidden.h to avoid
|
||||
// clashes with log.h from the Android SDK.
|
||||
// That header file uses
|
||||
// __attribute__ ((format(printf, 3, 4)))
|
||||
// which gets messed up by our override mechanism; this could
|
||||
// be avoided by either changing the Android SDK to use the equally
|
||||
// legal and valid
|
||||
// __attribute__ ((format(__printf__, 3, 4)))
|
||||
// or by refining our printf override to use a varadic macro
|
||||
// (which then wouldn't be portable, though).
|
||||
// Anyway, for now we just disable the printf override globally
|
||||
// for the Android port
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
|
||||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
#include "backends/networking/basic/android/jni.h"
|
||||
|
||||
#include "backends/networking/http/android/networkreadstream-android.h"
|
||||
#include "backends/networking/http/android/connectionmanager-android.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamAndroid(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamAndroid(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamAndroid(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
void NetworkReadStreamAndroid::gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers) {
|
||||
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
jsize size = env->GetArrayLength(headers);
|
||||
assert((size & 1) == 0);
|
||||
|
||||
stream->_responseHeadersMap.clear();
|
||||
for (jsize i = 0; i < size; i += 2) {
|
||||
jstring key_obj = (jstring)env->GetObjectArrayElement(headers, i);
|
||||
jstring value_obj = (jstring)env->GetObjectArrayElement(headers, i + 1);
|
||||
const char *key = env->GetStringUTFChars(key_obj, 0);
|
||||
const char *value = env->GetStringUTFChars(value_obj, 0);
|
||||
|
||||
if (key != nullptr && value != nullptr) {
|
||||
stream->_responseHeadersMap[key] = value;
|
||||
}
|
||||
env->ReleaseStringUTFChars(key_obj, key);
|
||||
env->ReleaseStringUTFChars(value_obj, value);
|
||||
|
||||
env->DeleteLocalRef(key_obj);
|
||||
env->DeleteLocalRef(value_obj);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamAndroid::gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize) {
|
||||
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
jsize arrSize = env->GetArrayLength(data);
|
||||
assert(size >= 0 && (jsize)size <= arrSize);
|
||||
|
||||
if (size > 0) {
|
||||
jbyte *dataP = (jbyte *)env->GetPrimitiveArrayCritical(data, 0);
|
||||
assert(dataP);
|
||||
|
||||
stream->_backingStream.write(dataP, size);
|
||||
stream->_downloaded += size;
|
||||
|
||||
env->ReleasePrimitiveArrayCritical(data, dataP, JNI_ABORT);
|
||||
}
|
||||
|
||||
stream->setProgress(stream->_downloaded, totalSize);
|
||||
}
|
||||
|
||||
void NetworkReadStreamAndroid::finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg) {
|
||||
NetworkReadStreamAndroid *stream = (NetworkReadStreamAndroid *)nativePointer;
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::String errorMsgStr;
|
||||
if (errorMsg) {
|
||||
const char *errorMsgP = env->GetStringUTFChars(errorMsg, 0);
|
||||
if (errorMsgP != 0) {
|
||||
errorMsgStr = Common::String(errorMsgP);
|
||||
env->ReleaseStringUTFChars(errorMsg, errorMsgP);
|
||||
}
|
||||
}
|
||||
|
||||
stream->finished(errorCode, errorMsgStr);
|
||||
}
|
||||
|
||||
static jobjectArray getHeaders(JNIEnv *env, RequestHeaders *headersList) {
|
||||
if (!headersList) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jclass stringClass = env->FindClass("java/lang/String");
|
||||
jobjectArray array = env->NewObjectArray(headersList->size() * 2, stringClass, nullptr);
|
||||
env->DeleteLocalRef(stringClass);
|
||||
|
||||
int i = 0;
|
||||
for (const Common::String &header : *headersList) {
|
||||
// Find the colon separator
|
||||
uint colonPos = header.findFirstOf(':');
|
||||
if (colonPos == Common::String::npos) {
|
||||
warning("NetworkReadStreamAndroid: Malformed header (no colon): %s", header.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split into key and value parts
|
||||
Common::String key = header.substr(0, colonPos);
|
||||
Common::String value = header.substr(colonPos + 1);
|
||||
|
||||
// Trim whitespace from key and value
|
||||
key.trim();
|
||||
value.trim();
|
||||
|
||||
jobject key_obj = env->NewStringUTF(key.c_str());
|
||||
jobject value_obj = env->NewStringUTF(value.c_str());
|
||||
|
||||
// Store key and value as separate strings
|
||||
env->SetObjectArrayElement(array, i, key_obj);
|
||||
env->SetObjectArrayElement(array, i + 1, value_obj);
|
||||
|
||||
env->DeleteLocalRef(key_obj);
|
||||
env->DeleteLocalRef(value_obj);
|
||||
|
||||
i += 2;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static jobjectArray getFormFields(JNIEnv *env, const Common::HashMap<Common::String, Common::String> &map) {
|
||||
jclass stringClass = env->FindClass("java/lang/String");
|
||||
jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
|
||||
env->DeleteLocalRef(stringClass);
|
||||
|
||||
int i = 0;
|
||||
for (const Common::HashMap<Common::String, Common::String>::Node &entry : map) {
|
||||
jobject key_obj = env->NewStringUTF(entry._key.c_str());
|
||||
jobject value_obj = env->NewStringUTF(entry._value.c_str());
|
||||
|
||||
// Store key and value as separate strings
|
||||
env->SetObjectArrayElement(array, i, key_obj);
|
||||
env->SetObjectArrayElement(array, i + 1, value_obj);
|
||||
|
||||
env->DeleteLocalRef(key_obj);
|
||||
env->DeleteLocalRef(value_obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static jobjectArray getFormFiles(JNIEnv *env, const Common::HashMap<Common::String, Common::Path> &map) {
|
||||
jclass stringClass = env->FindClass("java/lang/String");
|
||||
jobjectArray array = env->NewObjectArray(map.size() * 2, stringClass, nullptr);
|
||||
env->DeleteLocalRef(stringClass);
|
||||
|
||||
int i = 0;
|
||||
for (const Common::HashMap<Common::String, Common::Path>::Node &entry : map) {
|
||||
jobject key_obj = env->NewStringUTF(entry._key.c_str());
|
||||
jobject value_obj = env->NewStringUTF(entry._value.toString('/').c_str());
|
||||
|
||||
// Store key and value as separate strings
|
||||
env->SetObjectArrayElement(array, i, key_obj);
|
||||
env->SetObjectArrayElement(array, i + 1, value_obj);
|
||||
|
||||
env->DeleteLocalRef(key_obj);
|
||||
env->DeleteLocalRef(value_obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields,
|
||||
bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
|
||||
if (!reuse(url, headersList, postFields, uploading, usingPatch)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields,
|
||||
const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
|
||||
if (!reuse(url, headersList, formFields, formFiles)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReadStreamAndroid::NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
|
||||
bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval), _request(nullptr) {
|
||||
if (!reuse(url, headersList, buffer, bufferSize, uploading, usingPatch, post)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
resetStream(env);
|
||||
|
||||
jstring url_obj = env->NewStringUTF(url);
|
||||
jobjectArray headers_obj = getHeaders(env, headersList);
|
||||
jbyteArray uploadBuffer_obj = env->NewByteArray(postFields.size());
|
||||
env->SetByteArrayRegion(uploadBuffer_obj, 0, postFields.size(), (const jbyte *)postFields.c_str());
|
||||
|
||||
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
|
||||
|
||||
env->DeleteLocalRef(uploadBuffer_obj);
|
||||
env->DeleteLocalRef(headers_obj);
|
||||
env->DeleteLocalRef(url_obj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return false;
|
||||
}
|
||||
_request = env->NewGlobalRef(obj);
|
||||
env->DeleteLocalRef(obj);
|
||||
|
||||
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
resetStream(env);
|
||||
|
||||
jstring url_obj = env->NewStringUTF(url);
|
||||
jobjectArray headers_obj = getHeaders(env, headersList);
|
||||
jobjectArray formFields_obj = getFormFields(env, formFields);
|
||||
jobjectArray formFiles_obj = getFormFiles(env, formFiles);
|
||||
|
||||
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_forminit, (jlong)this, url_obj, headers_obj, formFields_obj, formFiles_obj);
|
||||
|
||||
env->DeleteLocalRef(formFiles_obj);
|
||||
env->DeleteLocalRef(formFields_obj);
|
||||
env->DeleteLocalRef(headers_obj);
|
||||
env->DeleteLocalRef(url_obj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
_request = env->NewGlobalRef(obj);
|
||||
env->DeleteLocalRef(obj);
|
||||
|
||||
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamAndroid::reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
resetStream(env);
|
||||
|
||||
jstring url_obj = env->NewStringUTF(url);
|
||||
jobjectArray headers_obj = getHeaders(env, headersList);
|
||||
jbyteArray uploadBuffer_obj = env->NewByteArray(bufferSize);
|
||||
env->SetByteArrayRegion(uploadBuffer_obj, 0, bufferSize, (const jbyte *)buffer);
|
||||
|
||||
jobject obj = env->NewObject(NetJNI::_CLS_HTTPRequest, NetJNI::_MID_request_bufinit, (jlong)this, url_obj, headers_obj, uploadBuffer_obj, uploading, usingPatch, false);
|
||||
|
||||
env->DeleteLocalRef(uploadBuffer_obj);
|
||||
env->DeleteLocalRef(headers_obj);
|
||||
env->DeleteLocalRef(url_obj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::<init> failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
_request = env->NewGlobalRef(obj);
|
||||
env->DeleteLocalRef(obj);
|
||||
|
||||
dynamic_cast<ConnectionManagerAndroid &>(ConnMan).registerRequest(env, _request);
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkReadStreamAndroid::~NetworkReadStreamAndroid() {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::cancel failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
env->DeleteGlobalRef(_request);
|
||||
}
|
||||
|
||||
void NetworkReadStreamAndroid::finished(int errorCode, const Common::String &errorMsg) {
|
||||
_requestComplete = true;
|
||||
_errorCode = errorCode;
|
||||
_errorMsg = errorMsg;
|
||||
|
||||
if (_errorCode >= 200 && _errorCode < 300) {
|
||||
debug(9, "NetworkReadStreamAndroid: %s - Request succeeded", currentLocation().c_str());
|
||||
} else {
|
||||
warning("NetworkReadStreamAndroid: %s - Request failed (%d - %s)", currentLocation().c_str(), _errorCode, _errorMsg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamAndroid::resetStream(JNIEnv *env) {
|
||||
_eos = _requestComplete = false;
|
||||
_progressDownloaded = _progressTotal = 0;
|
||||
|
||||
if (_request) {
|
||||
env->CallVoidMethod(_request, NetJNI::_MID_request_cancel);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::cancel failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
env->DeleteGlobalRef(_request);
|
||||
_request = nullptr;
|
||||
}
|
||||
|
||||
_responseHeadersMap.clear();
|
||||
_downloaded = 0;
|
||||
_errorCode = 0;
|
||||
_errorMsg.clear();
|
||||
}
|
||||
|
||||
Common::String NetworkReadStreamAndroid::currentLocation() const {
|
||||
if (!_request) {
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring location_obj = (jstring)env->CallObjectMethod(_request, NetJNI::_MID_request_getURL);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("HTTPRequest::getURL failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
uint length = env->GetStringLength(location_obj);
|
||||
if (!length) {
|
||||
env->DeleteLocalRef(location_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
const char *location_ptr = env->GetStringUTFChars(location_obj, 0);
|
||||
if (!location_ptr) {
|
||||
env->DeleteLocalRef(location_obj);
|
||||
return Common::String();
|
||||
}
|
||||
|
||||
Common::String result(location_ptr, length);
|
||||
|
||||
env->ReleaseStringUTFChars(location_obj, location_ptr);
|
||||
env->DeleteLocalRef(location_obj);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
82
backends/networking/http/android/networkreadstream-android.h
Normal file
82
backends/networking/http/android/networkreadstream-android.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
|
||||
#define BACKENDS_NETWORKING_HTTP_ANDROID_NETWORKREADSTREAM_ANDROID_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class NetworkReadStreamAndroid : public NetworkReadStream {
|
||||
friend class NetJNI;
|
||||
private:
|
||||
static void gotHeaders(JNIEnv *env, jobject obj, jlong nativePointer, jobjectArray headers);
|
||||
static void gotData(JNIEnv *env, jobject obj, jlong nativePointer, jbyteArray data, jint size, jint totalSize);
|
||||
static void finished_(JNIEnv *env, jobject obj, jlong nativePointer, jint errorCode, jstring errorMsg);
|
||||
|
||||
void resetStream(JNIEnv *env);
|
||||
void finished(int errorCode, const Common::String &errorMsg);
|
||||
|
||||
jobject _request;
|
||||
|
||||
Common::HashMap<Common::String, Common::String> _responseHeadersMap;
|
||||
uint64 _downloaded;
|
||||
int _errorCode;
|
||||
Common::String _errorMsg;
|
||||
public:
|
||||
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamAndroid(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
~NetworkReadStreamAndroid() override;
|
||||
|
||||
/** Send <postFields>, using POST by default. */
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override;
|
||||
/** Send <formFields>, <formFiles>, using POST multipart/form. */
|
||||
bool reuse(
|
||||
const char *url, RequestHeaders *headersList,
|
||||
const Common::HashMap<Common::String, Common::String> &formFields,
|
||||
const Common::HashMap<Common::String, Common::Path> &formFiles) override;
|
||||
/** Send <buffer>, using POST by default. */
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
|
||||
|
||||
long httpResponseCode() const override { return _errorCode; }
|
||||
Common::String currentLocation() const override;
|
||||
/**
|
||||
* Return response headers as HashMap. All header names in
|
||||
* it are lowercase.
|
||||
*
|
||||
* @note This method should be called when eos() == true.
|
||||
*/
|
||||
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override { return _responseHeadersMap; }
|
||||
|
||||
bool hasError() const override { return _errorCode < 200 || _errorCode >= 300; }
|
||||
const char *getError() const override { return _errorMsg.c_str(); }
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
167
backends/networking/http/connectionmanager.cpp
Normal file
167
backends/networking/http/connectionmanager.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/system.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
DECLARE_SINGLETON(Networking::ConnectionManager);
|
||||
|
||||
/* The makeInstance function is defined in the platform specific source file */
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace Networking {
|
||||
ConnectionManager::ConnectionManager() : _timerStarted(false), _frame(0) {}
|
||||
|
||||
ConnectionManager::~ConnectionManager() {
|
||||
stopTimer();
|
||||
|
||||
// terminate all added requests which haven't been processed yet
|
||||
_addedRequestsMutex.lock();
|
||||
for (auto &curRequest : _addedRequests) {
|
||||
Request *request = curRequest.request;
|
||||
RequestCallback callback = curRequest.onDeleteCallback;
|
||||
if (request)
|
||||
request->finish();
|
||||
delete request;
|
||||
if (callback) {
|
||||
(*callback)(request);
|
||||
delete callback;
|
||||
}
|
||||
}
|
||||
_addedRequests.clear();
|
||||
_addedRequestsMutex.unlock();
|
||||
|
||||
// terminate all requests
|
||||
_handleMutex.lock();
|
||||
for (auto &curRequest : _requests) {
|
||||
Request *request = curRequest.request;
|
||||
RequestCallback callback = curRequest.onDeleteCallback;
|
||||
if (request)
|
||||
request->finish();
|
||||
delete request;
|
||||
if (callback) {
|
||||
(*callback)(request);
|
||||
delete callback;
|
||||
}
|
||||
}
|
||||
_requests.clear();
|
||||
|
||||
_handleMutex.unlock();
|
||||
}
|
||||
|
||||
Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) {
|
||||
_addedRequestsMutex.lock();
|
||||
_addedRequests.push_back(RequestWithCallback(request, callback));
|
||||
if (!_timerStarted)
|
||||
startTimer();
|
||||
_addedRequestsMutex.unlock();
|
||||
return request;
|
||||
}
|
||||
|
||||
uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
|
||||
return TIMER_INTERVAL * ITERATION_PERIOD;
|
||||
}
|
||||
|
||||
// private goes here:
|
||||
|
||||
void connectionsThread(void *ignored) {
|
||||
ConnMan.handle();
|
||||
}
|
||||
|
||||
void ConnectionManager::startTimer(int interval) {
|
||||
Common::TimerManager *manager = g_system->getTimerManager();
|
||||
if (manager->installTimerProc(connectionsThread, interval, nullptr, "Networking::ConnectionManager's Timer")) {
|
||||
_timerStarted = true;
|
||||
} else {
|
||||
warning("Failed to install Networking::ConnectionManager's timer");
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionManager::stopTimer() {
|
||||
debug(9, "timer stopped");
|
||||
Common::TimerManager *manager = g_system->getTimerManager();
|
||||
manager->removeTimerProc(connectionsThread);
|
||||
_timerStarted = false;
|
||||
}
|
||||
|
||||
bool ConnectionManager::hasAddedRequests() {
|
||||
_addedRequestsMutex.lock();
|
||||
bool hasNewRequests = !_addedRequests.empty();
|
||||
_addedRequestsMutex.unlock();
|
||||
return hasNewRequests;
|
||||
}
|
||||
|
||||
void ConnectionManager::handle() {
|
||||
// lock mutex here (in case another handle() would be called before this one ends)
|
||||
_handleMutex.lock();
|
||||
++_frame;
|
||||
if (_frame % ITERATION_PERIOD == 0)
|
||||
iterateRequests();
|
||||
if (_frame % PROCESSING_PERIOD == 0)
|
||||
processTransfers();
|
||||
|
||||
if (_requests.empty() && !hasAddedRequests())
|
||||
stopTimer();
|
||||
_handleMutex.unlock();
|
||||
}
|
||||
|
||||
void ConnectionManager::iterateRequests() {
|
||||
// add new requests
|
||||
_addedRequestsMutex.lock();
|
||||
for (auto &addedRequest : _addedRequests) {
|
||||
_requests.push_back(addedRequest);
|
||||
}
|
||||
_addedRequests.clear();
|
||||
_addedRequestsMutex.unlock();
|
||||
|
||||
// call handle() of all running requests (so they can do their work)
|
||||
if (_frame % DEBUG_PRINT_PERIOD == 0)
|
||||
debug(9, "handling %d request(s)", _requests.size());
|
||||
for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) {
|
||||
Request *request = i->request;
|
||||
if (request) {
|
||||
if (request->state() == PROCESSING)
|
||||
request->handle();
|
||||
else if (request->state() == RETRY)
|
||||
request->handleRetry();
|
||||
}
|
||||
|
||||
if (!request || request->state() == FINISHED) {
|
||||
delete (i->request);
|
||||
if (i->onDeleteCallback) {
|
||||
(*i->onDeleteCallback)(i->request); // that's not a mistake (we're passing an address and that method knows there is no object anymore)
|
||||
delete i->onDeleteCallback;
|
||||
}
|
||||
_requests.erase(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
115
backends/networking/http/connectionmanager.h
Normal file
115
backends/networking/http/connectionmanager.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_CONNECTIONMANAGER_H
|
||||
#define BACKENDS_NETWORKING_HTTP_CONNECTIONMANAGER_H
|
||||
|
||||
#include "backends/networking/http/request.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ConnectionManager : public Common::Singleton<Networking::ConnectionManager> {
|
||||
static const uint32 FRAMES_PER_SECOND = 100;
|
||||
static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
|
||||
static const uint32 ITERATION_PERIOD = 1; // every frame
|
||||
static const uint32 PROCESSING_PERIOD = 1; // every frame
|
||||
static const uint32 DEBUG_PRINT_PERIOD = FRAMES_PER_SECOND; // once per second
|
||||
|
||||
friend void connectionsThread(void *); // calls handle()
|
||||
|
||||
typedef Common::BaseCallback<Request *> *RequestCallback;
|
||||
|
||||
/**
|
||||
* RequestWithCallback is used by ConnectionManager to
|
||||
* storage the Request and a callback which should be
|
||||
* called on Request delete.
|
||||
*
|
||||
* Usually one won't need to pass such callback, but
|
||||
* in some cases you'd like to know whether Request is
|
||||
* still running.
|
||||
*
|
||||
* For example, Cloud::Storage is keeping track of how
|
||||
* many Requests are running, and thus it needs to know
|
||||
* that Request was destroyed to decrease its counter.
|
||||
*
|
||||
* onDeleteCallback is called with *invalid* pointer.
|
||||
* ConnectionManager deletes Request first and then passes
|
||||
* the pointer to the callback. One may use the address
|
||||
* to find it in own HashMap or Array and remove it.
|
||||
* So, again, this pointer is for information only. One
|
||||
* cannot use it.
|
||||
*/
|
||||
struct RequestWithCallback {
|
||||
Request *request;
|
||||
RequestCallback onDeleteCallback;
|
||||
|
||||
RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr) : request(rq), onDeleteCallback(cb) {}
|
||||
};
|
||||
|
||||
bool _timerStarted;
|
||||
Common::Array<RequestWithCallback> _requests, _addedRequests;
|
||||
Common::Mutex _handleMutex, _addedRequestsMutex;
|
||||
uint32 _frame;
|
||||
|
||||
void startTimer(int interval = TIMER_INTERVAL);
|
||||
void stopTimer();
|
||||
void handle();
|
||||
void iterateRequests();
|
||||
virtual void processTransfers() = 0;
|
||||
bool hasAddedRequests();
|
||||
|
||||
public:
|
||||
ConnectionManager();
|
||||
~ConnectionManager();
|
||||
|
||||
/**
|
||||
* Use this method to add new Request into manager's queue.
|
||||
* Manager will periodically call handle() method of these
|
||||
* Requests until they set their state to FINISHED.
|
||||
*
|
||||
* If Request's state is RETRY, handleRetry() is called instead.
|
||||
*
|
||||
* The passed callback would be called after Request is deleted.
|
||||
*
|
||||
* @note This method starts the timer if it's not started yet.
|
||||
*
|
||||
* @return the same Request pointer, just as a shortcut
|
||||
*/
|
||||
Request *addRequest(Request *request, RequestCallback callback = nullptr);
|
||||
|
||||
static uint32 getCloudRequestsPeriodInMicroseconds();
|
||||
};
|
||||
|
||||
/** Shortcut for accessing the connection manager. */
|
||||
#define ConnMan Networking::ConnectionManager::instance()
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
namespace Common {
|
||||
template<>
|
||||
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance();
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
||||
92
backends/networking/http/curl/connectionmanager-curl.cpp
Normal file
92
backends/networking/http/curl/connectionmanager-curl.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/curl/connectionmanager-curl.h"
|
||||
#include "backends/networking/http/curl/networkreadstream-curl.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/system.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template<>
|
||||
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
|
||||
return new Networking::ConnectionManagerCurl();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace Networking {
|
||||
/* Workaround a MSVC bug from MSVC 2015
|
||||
* The compiler considers this template specialization as inline.
|
||||
* If this TU doesn't use the function, it is then discarded.
|
||||
*/
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
|
||||
void dummyFunction() {
|
||||
ConnMan;
|
||||
}
|
||||
#endif
|
||||
|
||||
ConnectionManagerCurl::ConnectionManagerCurl() : ConnectionManager(), _multi(nullptr) {
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
_multi = curl_multi_init();
|
||||
}
|
||||
|
||||
ConnectionManagerCurl::~ConnectionManagerCurl() {
|
||||
// cleanup
|
||||
curl_multi_cleanup(_multi);
|
||||
curl_global_cleanup();
|
||||
_multi = nullptr;
|
||||
}
|
||||
|
||||
void ConnectionManagerCurl::registerEasyHandle(CURL *easy) const {
|
||||
curl_multi_add_handle(_multi, easy);
|
||||
}
|
||||
|
||||
// private goes here:
|
||||
void ConnectionManagerCurl::processTransfers() {
|
||||
if (!_multi)
|
||||
return;
|
||||
|
||||
// check libcurl's transfers and notify requests of messages from queue (transfer completion or failure)
|
||||
int transfersRunning;
|
||||
curl_multi_perform(_multi, &transfersRunning);
|
||||
|
||||
int messagesInQueue;
|
||||
CURLMsg *curlMsg;
|
||||
while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) {
|
||||
if (curlMsg->msg == CURLMSG_DONE) {
|
||||
CURL *easyHandle = curlMsg->easy_handle;
|
||||
|
||||
NetworkReadStreamCurl *stream = nullptr;
|
||||
curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
|
||||
|
||||
if (stream)
|
||||
stream->finished(curlMsg->data.result);
|
||||
|
||||
curl_multi_remove_handle(_multi, easyHandle);
|
||||
} else {
|
||||
warning("Unknown libcurl message type %d", curlMsg->msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
54
backends/networking/http/curl/connectionmanager-curl.h
Normal file
54
backends/networking/http/curl/connectionmanager-curl.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_CURL_CONNECTIONMANAGERCURL_H
|
||||
#define BACKENDS_NETWORKING_HTTP_CURL_CONNECTIONMANAGERCURL_H
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ConnectionManagerCurl : public ConnectionManager {
|
||||
private:
|
||||
CURLM *_multi;
|
||||
|
||||
void processTransfers() override;
|
||||
|
||||
public:
|
||||
ConnectionManagerCurl();
|
||||
~ConnectionManagerCurl() override;
|
||||
|
||||
/**
|
||||
* All libcurl transfers are going through this ConnectionManager.
|
||||
* So, if you want to start any libcurl transfer, you must create
|
||||
* an easy handle and register it using this method.
|
||||
*/
|
||||
void registerEasyHandle(CURL *easy) const;
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
394
backends/networking/http/curl/networkreadstream-curl.cpp
Normal file
394
backends/networking/http/curl/networkreadstream-curl.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
#define CURL_DISABLE_DEPRECATION
|
||||
|
||||
#include "backends/networking/basic/curl/cacert.h"
|
||||
#include "backends/networking/http/curl/networkreadstream-curl.h"
|
||||
#include "backends/networking/http/curl/connectionmanager-curl.h"
|
||||
#include "base/version.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamCurl(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamCurl(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamCurl(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
size_t NetworkReadStreamCurl::curlDataCallback(char *d, size_t n, size_t l, void *p) {
|
||||
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
|
||||
if (stream)
|
||||
return stream->_backingStream.write(d, n * l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t NetworkReadStreamCurl::curlReadDataCallback(char *d, size_t n, size_t l, void *p) {
|
||||
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
|
||||
if (stream)
|
||||
return stream->fillWithSendingContents(d, n * l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t NetworkReadStreamCurl::curlHeadersCallback(char *d, size_t n, size_t l, void *p) {
|
||||
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
|
||||
if (stream)
|
||||
return stream->addResponseHeaders(d, n * l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
|
||||
NetworkReadStreamCurl *stream = (NetworkReadStreamCurl *)p;
|
||||
if (stream)
|
||||
stream->setProgress(dlnow, dltotal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NetworkReadStreamCurl::curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) {
|
||||
// for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION)
|
||||
return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
|
||||
}
|
||||
|
||||
void NetworkReadStreamCurl::resetStream() {
|
||||
_eos = _requestComplete = false;
|
||||
if (!_errorBuffer)
|
||||
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
|
||||
_sendingContentsBuffer = nullptr;
|
||||
_sendingContentsSize = _sendingContentsPos = 0;
|
||||
_progressDownloaded = _progressTotal = 0;
|
||||
_bufferCopy = nullptr;
|
||||
if (_headersSlist) {
|
||||
curl_slist_free_all(_headersSlist);
|
||||
_headersSlist = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamCurl::initCurl(const char *url, RequestHeaders *headersList) {
|
||||
resetStream();
|
||||
|
||||
_easy = curl_easy_init();
|
||||
curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
|
||||
curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); // so callback can call us
|
||||
curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); // so ConnectionManager can call us when request is complete
|
||||
curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
|
||||
curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
|
||||
curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
|
||||
curl_easy_setopt(_easy, CURLOPT_URL, url);
|
||||
curl_easy_setopt(_easy, CURLOPT_ERRORBUFFER, _errorBuffer);
|
||||
curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
|
||||
curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); // probably it's OK to have it always on
|
||||
|
||||
// Convert headers to curl_slist format
|
||||
_headersSlist = requestHeadersToSlist(headersList);
|
||||
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, _headersSlist);
|
||||
|
||||
curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
|
||||
curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
|
||||
curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
|
||||
|
||||
#if defined NINTENDO_SWITCH || defined PSP2
|
||||
curl_easy_setopt(_easy, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
#endif
|
||||
|
||||
Common::String caCertPath = getCaCertPath();
|
||||
if (!caCertPath.empty()) {
|
||||
curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath.c_str());
|
||||
}
|
||||
|
||||
#if LIBCURL_VERSION_NUM >= 0x072000
|
||||
// CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
|
||||
// CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
|
||||
curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
|
||||
curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
|
||||
#endif
|
||||
|
||||
#if LIBCURL_VERSION_NUM >= 0x071900
|
||||
// Added in libcurl 7.25.0
|
||||
if (_keepAlive) {
|
||||
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPALIVE, 1L);
|
||||
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPIDLE, _keepAliveIdle);
|
||||
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPINTVL, _keepAliveInterval);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NetworkReadStreamCurl::reuseCurl(const char *url, RequestHeaders *headersList) {
|
||||
if (!_keepAlive) {
|
||||
warning("NetworkReadStream: Can't reuse curl handle (was not setup as keep-alive)");
|
||||
return false;
|
||||
}
|
||||
|
||||
resetStream();
|
||||
|
||||
_headersSlist = requestHeadersToSlist(headersList);
|
||||
curl_easy_setopt(_easy, CURLOPT_URL, url);
|
||||
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, _headersSlist);
|
||||
curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); // in case headersList rewrites it
|
||||
|
||||
return true;
|
||||
}
|
||||
void NetworkReadStreamCurl::setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
|
||||
if (uploading) {
|
||||
curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(_easy, CURLOPT_READDATA, this);
|
||||
curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback);
|
||||
_sendingContentsBuffer = buffer;
|
||||
_sendingContentsSize = bufferSize;
|
||||
} else if (usingPatch) {
|
||||
curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
} else {
|
||||
if (post || bufferSize != 0) {
|
||||
curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize);
|
||||
#if LIBCURL_VERSION_NUM >= 0x071101
|
||||
// CURLOPT_COPYPOSTFIELDS available since curl 7.17.1
|
||||
curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer);
|
||||
#else
|
||||
_bufferCopy = (byte *)malloc(bufferSize);
|
||||
memcpy(_bufferCopy, buffer, bufferSize);
|
||||
curl_easy_setopt(_easy, CURLOPT_POSTFIELDS, _bufferCopy);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
dynamic_cast<ConnectionManagerCurl &>(ConnMan).registerEasyHandle(_easy);
|
||||
}
|
||||
|
||||
void NetworkReadStreamCurl::setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
|
||||
struct curl_httppost *formpost = nullptr;
|
||||
struct curl_httppost *lastptr = nullptr;
|
||||
|
||||
for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) {
|
||||
CURLFORMcode code = curl_formadd(
|
||||
&formpost,
|
||||
&lastptr,
|
||||
CURLFORM_COPYNAME, i->_key.c_str(),
|
||||
CURLFORM_COPYCONTENTS, i->_value.c_str(),
|
||||
CURLFORM_END);
|
||||
|
||||
if (code != CURL_FORMADD_OK)
|
||||
warning("NetworkReadStreamCurl: field curl_formadd('%s') failed", i->_key.c_str());
|
||||
}
|
||||
|
||||
for (Common::HashMap<Common::String, Common::Path>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) {
|
||||
CURLFORMcode code = curl_formadd(
|
||||
&formpost,
|
||||
&lastptr,
|
||||
CURLFORM_COPYNAME, i->_key.c_str(),
|
||||
CURLFORM_FILE, i->_value.toString(Common::Path::kNativeSeparator).c_str(),
|
||||
CURLFORM_END);
|
||||
|
||||
if (code != CURL_FORMADD_OK)
|
||||
warning("NetworkReadStreamCurl: file curl_formadd('%s') failed", i->_key.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
|
||||
dynamic_cast<ConnectionManagerCurl &>(ConnMan).registerEasyHandle(_easy);
|
||||
}
|
||||
|
||||
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
|
||||
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
|
||||
_errorBuffer(nullptr), _headersSlist(nullptr) {
|
||||
initCurl(url, headersList);
|
||||
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
|
||||
}
|
||||
|
||||
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
|
||||
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
|
||||
_errorBuffer(nullptr), _headersSlist(nullptr) {
|
||||
initCurl(url, headersList);
|
||||
setupFormMultipart(formFields, formFiles);
|
||||
}
|
||||
|
||||
NetworkReadStreamCurl::NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval)
|
||||
: NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval),
|
||||
_errorBuffer(nullptr), _headersSlist(nullptr) {
|
||||
initCurl(url, headersList);
|
||||
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
|
||||
}
|
||||
|
||||
curl_slist *NetworkReadStreamCurl::requestHeadersToSlist(const RequestHeaders *headersList) {
|
||||
curl_slist *slist = nullptr;
|
||||
if (headersList) {
|
||||
for (const Common::String &header : *headersList) {
|
||||
slist = curl_slist_append(slist, header.c_str());
|
||||
}
|
||||
}
|
||||
return slist;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch) {
|
||||
if (!reuseCurl(url, headersList))
|
||||
return false;
|
||||
|
||||
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
|
||||
if (!reuseCurl(url, headersList))
|
||||
return false;
|
||||
|
||||
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
setupFormMultipart(formFields, formFiles);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamCurl::reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
|
||||
if (!reuseCurl(url, headersList))
|
||||
return false;
|
||||
|
||||
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
||||
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkReadStreamCurl::~NetworkReadStreamCurl() {
|
||||
if (_easy)
|
||||
curl_easy_cleanup(_easy);
|
||||
free(_bufferCopy);
|
||||
free(_errorBuffer);
|
||||
if (_headersSlist) {
|
||||
curl_slist_free_all(_headersSlist);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamCurl::finished(CURLcode errorCode) {
|
||||
_requestComplete = true;
|
||||
|
||||
char *url = nullptr;
|
||||
curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &url);
|
||||
|
||||
_errorCode = errorCode;
|
||||
|
||||
if (_errorCode == CURLE_OK) {
|
||||
debug(9, "NetworkReadStreamCurl: %s - Request succeeded", url);
|
||||
} else {
|
||||
warning("NetworkReadStreamCurl: %s - Request failed (%d - %s)", url, _errorCode, getError());
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkReadStreamCurl::hasError() const {
|
||||
return _errorCode != CURLE_OK;
|
||||
}
|
||||
|
||||
const char *NetworkReadStreamCurl::getError() const {
|
||||
return strlen(_errorBuffer) ? _errorBuffer : curl_easy_strerror(_errorCode);
|
||||
}
|
||||
|
||||
long NetworkReadStreamCurl::httpResponseCode() const {
|
||||
long responseCode = -1;
|
||||
if (_easy)
|
||||
curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode);
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
Common::String NetworkReadStreamCurl::currentLocation() const {
|
||||
Common::String result = "";
|
||||
if (_easy) {
|
||||
char *pointer;
|
||||
curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer);
|
||||
result = Common::String(pointer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::HashMap<Common::String, Common::String> NetworkReadStreamCurl::responseHeadersMap() const {
|
||||
// HTTP headers are described at RFC 2616: https://datatracker.ietf.org/doc/html/rfc2616#section-4.2
|
||||
// this implementation tries to follow it, but for simplicity it does not support multi-line header values
|
||||
|
||||
Common::HashMap<Common::String, Common::String> headers;
|
||||
Common::String headerName, headerValue, trailingWhitespace;
|
||||
char c;
|
||||
bool readingName = true;
|
||||
|
||||
for (uint i = 0; i < _responseHeaders.size(); ++i) {
|
||||
c = _responseHeaders[i];
|
||||
|
||||
if (readingName) {
|
||||
if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
|
||||
// header names should not contain any whitespace, this is invalid
|
||||
// ignore what's been before
|
||||
headerName = "";
|
||||
continue;
|
||||
}
|
||||
if (c == ':') {
|
||||
if (!headerName.empty()) {
|
||||
readingName = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
headerName += c;
|
||||
continue;
|
||||
}
|
||||
|
||||
// reading value:
|
||||
if (c == ' ' || c == '\t') {
|
||||
if (headerValue.empty()) {
|
||||
// skip leading whitespace
|
||||
continue;
|
||||
} else {
|
||||
// accumulate trailing whitespace
|
||||
trailingWhitespace += c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\r' || c == '\n') {
|
||||
// not sure if RFC allows empty values, we'll ignore such
|
||||
if (!headerName.empty() && !headerValue.empty()) {
|
||||
// add header value
|
||||
// RFC allows header with the same name to be sent multiple times
|
||||
// and requires it to be equivalent of just listing all header values separated with comma
|
||||
// so if header already was met, we'll add new value to the old one
|
||||
headerName.toLowercase();
|
||||
if (headers.contains(headerName)) {
|
||||
headers[headerName] += "," + headerValue;
|
||||
} else {
|
||||
headers[headerName] = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
headerName = "";
|
||||
headerValue = "";
|
||||
trailingWhitespace = "";
|
||||
readingName = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we meet non-whitespace character, turns out those "trailing" whitespace characters were not so trailing
|
||||
headerValue += trailingWhitespace;
|
||||
trailingWhitespace = "";
|
||||
headerValue += c;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
97
backends/networking/http/curl/networkreadstream-curl.h
Normal file
97
backends/networking/http/curl/networkreadstream-curl.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_CURL_NETWORKREADSTREAMCURL_H
|
||||
#define BACKENDS_NETWORKING_HTTP_CURL_NETWORKREADSTREAMCURL_H
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/path.h"
|
||||
#include "common/str.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class NetworkReadStreamCurl : public NetworkReadStream {
|
||||
private:
|
||||
CURL *_easy;
|
||||
struct curl_slist *_headersSlist;
|
||||
char *_errorBuffer;
|
||||
CURLcode _errorCode;
|
||||
byte *_bufferCopy; // To use with old curl version where CURLOPT_COPYPOSTFIELDS is not available
|
||||
void initCurl(const char *url, RequestHeaders *headersList);
|
||||
bool reuseCurl(const char *url, RequestHeaders *headersList);
|
||||
static struct curl_slist *requestHeadersToSlist(const RequestHeaders *headersList);
|
||||
|
||||
static size_t curlDataCallback(char *d, size_t n, size_t l, void *p);
|
||||
static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p);
|
||||
static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p);
|
||||
static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
|
||||
// CURL-specific methods
|
||||
CURL *getEasyHandle() const { return _easy; }
|
||||
void resetStream();
|
||||
void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
|
||||
void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
|
||||
|
||||
public:
|
||||
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamCurl(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
~NetworkReadStreamCurl();
|
||||
void finished(CURLcode errorCode);
|
||||
|
||||
/** Send <postFields>, using POST by default. */
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override;
|
||||
/** Send <formFields>, <formFiles>, using POST multipart/form. */
|
||||
bool reuse(
|
||||
const char *url, RequestHeaders *headersList,
|
||||
const Common::HashMap<Common::String, Common::String> &formFields,
|
||||
const Common::HashMap<Common::String, Common::Path> &formFiles) override;
|
||||
/** Send <buffer>, using POST by default. */
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) override;
|
||||
|
||||
long httpResponseCode() const override;
|
||||
Common::String currentLocation() const override;
|
||||
/**
|
||||
* Return response headers as HashMap. All header names in
|
||||
* it are lowercase.
|
||||
*
|
||||
* @note This method should be called when eos() == true.
|
||||
*/
|
||||
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override;
|
||||
|
||||
bool hasError() const override;
|
||||
const char *getError() const override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_asctime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_clock
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_ctime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_difftime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_getdate
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_gmtime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_localtime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_mktime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_strcpy
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_strdup
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_time
|
||||
|
||||
#include "backends/networking/http/emscripten/connectionmanager-emscripten.h"
|
||||
#include "common/debug.h"
|
||||
#include <emscripten.h>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template<>
|
||||
Networking::ConnectionManager *Singleton<Networking::ConnectionManager>::makeInstance() {
|
||||
return new Networking::ConnectionManagerEmscripten();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
namespace Networking {
|
||||
|
||||
void ConnectionManagerEmscripten::processTransfers() {
|
||||
// Emscripten handles transfers asynchronously via callbacks
|
||||
// No action needed here
|
||||
}
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif // EMSCRIPTEN
|
||||
@@ -0,0 +1,41 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_CONNECTIONMANAGEREMSCRIPTEN_H
|
||||
#define BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_CONNECTIONMANAGEREMSCRIPTEN_H
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class ConnectionManagerEmscripten : public ConnectionManager {
|
||||
|
||||
public:
|
||||
void processTransfers() override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,307 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_asctime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_clock
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_ctime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_difftime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_getdate
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_gmtime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_localtime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_mktime
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_strcpy
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_strdup
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_time
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/fetch.h>
|
||||
|
||||
#include "backends/networking/http/emscripten/networkreadstream-emscripten.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "base/version.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamEmscripten(url, headersList, postFields, uploading, usingPatch, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamEmscripten(url, headersList, formFields, formFiles, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
NetworkReadStream *NetworkReadStream::make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) {
|
||||
return new NetworkReadStreamEmscripten(url, headersList, buffer, bufferSize, uploading, usingPatch, post, keepAlive, keepAliveIdle, keepAliveInterval);
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::emscriptenOnReadyStateChange(emscripten_fetch_t *fetch) {
|
||||
if (fetch->readyState != 2)
|
||||
return;
|
||||
|
||||
size_t headersLengthBytes = emscripten_fetch_get_response_headers_length(fetch) + 1;
|
||||
char *headerString = (char *)malloc(headersLengthBytes);
|
||||
|
||||
assert(headerString);
|
||||
emscripten_fetch_get_response_headers(fetch, headerString, headersLengthBytes);
|
||||
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
|
||||
stream->addResponseHeaders(headerString, headersLengthBytes);
|
||||
free(headerString);
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::emscriptenOnProgress(emscripten_fetch_t *fetch) {
|
||||
/*
|
||||
if (fetch->totalBytes) {
|
||||
debug(5,"Downloading %s.. %.2f percent complete.", fetch->url, fetch->dataOffset * 100.0 / fetch->totalBytes);
|
||||
} else {
|
||||
debug(5,"Downloading %s.. %lld bytes complete.", fetch->url, fetch->dataOffset + fetch->numBytes);
|
||||
}
|
||||
debug(5,"Downloading %s.. %.2f %s complete. HTTP readyState: %hu. HTTP status: %hu - "
|
||||
"HTTP statusText: %s. Received chunk [%llu, %llu]",
|
||||
fetch->url,
|
||||
fetch->totalBytes > 0 ? (fetch->dataOffset + fetch->numBytes) * 100.0 / fetch->totalBytes : (fetch->dataOffset + fetch->numBytes),
|
||||
fetch->totalBytes > 0 ? "percent" : " bytes",
|
||||
fetch->readyState,
|
||||
fetch->status,
|
||||
fetch->statusText,
|
||||
fetch->dataOffset,
|
||||
fetch->dataOffset + fetch->numBytes);
|
||||
*/
|
||||
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
|
||||
if (stream) {
|
||||
stream->setProgress(fetch->dataOffset, fetch->totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::emscriptenOnSuccess(emscripten_fetch_t *fetch) {
|
||||
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
|
||||
stream->emscriptenDownloadFinished(true);
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::emscriptenOnError(emscripten_fetch_t *fetch) {
|
||||
NetworkReadStreamEmscripten *stream = (NetworkReadStreamEmscripten *)fetch->userData;
|
||||
stream->emscriptenDownloadFinished(false);
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::emscriptenDownloadFinished(bool success) {
|
||||
_requestComplete = true;
|
||||
if (_emscripten_fetch->numBytes > 0) {
|
||||
// TODO: This could be done continuously during emscriptenOnProgress?
|
||||
this->_backingStream.write(_emscripten_fetch->data, _emscripten_fetch->numBytes);
|
||||
}
|
||||
this->setProgress(_emscripten_fetch->numBytes, _emscripten_fetch->numBytes);
|
||||
|
||||
if (success) {
|
||||
debug(5, "NetworkReadStreamEmscripten::emscriptenHandleDownload Finished downloading %llu bytes from URL %s. HTTP status code: %d", _emscripten_fetch->numBytes, _emscripten_fetch->url, _emscripten_fetch->status);
|
||||
_success = true; // TODO: actually pass the result code from emscripten_fetch
|
||||
} else {
|
||||
debug(5, "NetworkReadStreamEmscripten::emscriptenHandleDownload Downloading %s failed, HTTP failure status code: %d, status text: %s", _emscripten_fetch->url, _emscripten_fetch->status, _emscripten_fetch->statusText);
|
||||
|
||||
// Make a copy of the error message since _emscripten_fetch might be cleaned up
|
||||
if (_emscripten_fetch && _emscripten_fetch->statusText) {
|
||||
_errorBuffer = strdup(_emscripten_fetch->statusText);
|
||||
} else {
|
||||
_errorBuffer = strdup("Unknown error");
|
||||
}
|
||||
warning("NetworkReadStreamEmscripten::finished %s - Request failed (%s)", _emscripten_fetch_url, getError());
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::resetStream() {
|
||||
_eos = _requestComplete = false;
|
||||
_sendingContentsSize = _sendingContentsPos = 0;
|
||||
_progressDownloaded = _progressTotal = 0;
|
||||
_emscripten_fetch = nullptr;
|
||||
_emscripten_request_headers = nullptr;
|
||||
free(_errorBuffer);
|
||||
_errorBuffer = nullptr;
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::initEmscripten(const char *url, RequestHeaders *headersList) {
|
||||
|
||||
resetStream();
|
||||
emscripten_fetch_attr_init(_emscripten_fetch_attr);
|
||||
|
||||
// convert header list
|
||||
// first get size of list
|
||||
int size = 0;
|
||||
if (headersList) {
|
||||
size = headersList->size();
|
||||
debug(5, "_emscripten_request_headers count: %d", size);
|
||||
}
|
||||
_emscripten_request_headers = new char *[size * 2 + 1];
|
||||
_emscripten_request_headers[size * 2] = 0; // header array needs to be null-terminated.
|
||||
|
||||
int i = 0;
|
||||
if (headersList) {
|
||||
for (const Common::String &header : *headersList) {
|
||||
// Find the colon separator
|
||||
uint colonPos = header.findFirstOf(':');
|
||||
if (colonPos == Common::String::npos) {
|
||||
warning("NetworkReadStreamEmscripten: Malformed header (no colon): %s", header.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split into key and value parts
|
||||
Common::String key = header.substr(0, colonPos);
|
||||
Common::String value = header.substr(colonPos + 1);
|
||||
|
||||
// Trim whitespace from key and value
|
||||
key.trim();
|
||||
value.trim();
|
||||
|
||||
// Store key and value as separate strings
|
||||
_emscripten_request_headers[i++] = strdup(key.c_str());
|
||||
_emscripten_request_headers[i++] = strdup(value.c_str());
|
||||
debug(9, "_emscripten_request_headers key='%s' value='%s'", key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
_emscripten_fetch_attr->requestHeaders = _emscripten_request_headers;
|
||||
strcpy(_emscripten_fetch_attr->requestMethod, "GET"); // todo: move down to setup buffer contents
|
||||
_emscripten_fetch_attr->attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
|
||||
_emscripten_fetch_attr->onerror = emscriptenOnError;
|
||||
_emscripten_fetch_attr->onprogress = emscriptenOnProgress;
|
||||
_emscripten_fetch_attr->onreadystatechange = emscriptenOnReadyStateChange;
|
||||
_emscripten_fetch_attr->onsuccess = emscriptenOnSuccess;
|
||||
_emscripten_fetch_attr->userData = this;
|
||||
}
|
||||
void NetworkReadStreamEmscripten::setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
|
||||
if (uploading) {
|
||||
strcpy(_emscripten_fetch_attr->requestMethod, "PUT");
|
||||
_emscripten_fetch_attr->requestDataSize = bufferSize;
|
||||
_emscripten_fetch_attr->requestData = (const char *)buffer;
|
||||
} else if (usingPatch) {
|
||||
strcpy(_emscripten_fetch_attr->requestMethod, "PATCH");
|
||||
} else {
|
||||
if (post || bufferSize != 0) {
|
||||
strcpy(_emscripten_fetch_attr->requestMethod, "POST");
|
||||
_emscripten_fetch_attr->requestDataSize = bufferSize;
|
||||
_emscripten_fetch_attr->requestData = (const char *)buffer;
|
||||
}
|
||||
}
|
||||
debug(5, "NetworkReadStreamEmscripten::setupBufferContents uploading %s usingPatch %s post %s ->method %s", uploading ? "true" : "false", usingPatch ? "true" : "false", post ? "true" : "false", _emscripten_fetch_attr->requestMethod);
|
||||
_emscripten_fetch = emscripten_fetch(_emscripten_fetch_attr, _emscripten_fetch_url);
|
||||
}
|
||||
|
||||
void NetworkReadStreamEmscripten::setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) {
|
||||
// set POST multipart upload form fields/files
|
||||
error("NetworkReadStreamEmscripten::setupFormMultipart not implemented");
|
||||
}
|
||||
|
||||
/** Send <postFields>, using POST by default. */
|
||||
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::String &postFields,
|
||||
bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
|
||||
_emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
|
||||
initEmscripten(url, headersList);
|
||||
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
|
||||
}
|
||||
/** Send <formFields>, <formFiles>, using POST multipart/form. */
|
||||
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String,
|
||||
Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle,
|
||||
long keepAliveInterval) : _emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
|
||||
initEmscripten(url, headersList);
|
||||
setupFormMultipart(formFields, formFiles);
|
||||
}
|
||||
|
||||
/** Send <buffer>, using POST by default. */
|
||||
NetworkReadStreamEmscripten::NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
|
||||
bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval) :
|
||||
_emscripten_fetch_attr(new emscripten_fetch_attr_t()), _emscripten_fetch_url(url), _errorBuffer(nullptr),
|
||||
NetworkReadStream(keepAlive, keepAliveIdle, keepAliveInterval) {
|
||||
initEmscripten(url, headersList);
|
||||
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
|
||||
}
|
||||
|
||||
NetworkReadStreamEmscripten::~NetworkReadStreamEmscripten() {
|
||||
if (_emscripten_fetch) {
|
||||
debug(5, "~NetworkReadStreamEmscripten: emscripten_fetch_close");
|
||||
emscripten_fetch_close(_emscripten_fetch);
|
||||
}
|
||||
|
||||
// Free the headers array and its contents
|
||||
if (_emscripten_request_headers) {
|
||||
for (int i = 0; _emscripten_request_headers[i] != nullptr; ++i) {
|
||||
free(_emscripten_request_headers[i]); // Free each strdup'd string
|
||||
}
|
||||
delete[] _emscripten_request_headers;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 NetworkReadStreamEmscripten::read(void *dataPtr, uint32 dataSize) {
|
||||
uint32 actuallyRead = _backingStream.read(dataPtr, dataSize);
|
||||
|
||||
// Only access _emscripten_fetch->url if _emscripten_fetch is valid
|
||||
// debug(5,"NetworkReadStreamEmscripten::read %u %s %s %s", actuallyRead, _eos ? "_eos" : "not _eos", _requestComplete ? "_requestComplete" : "_request not Complete", _emscripten_fetch ? _emscripten_fetch->url : "no-url");
|
||||
if (actuallyRead == 0) {
|
||||
if (_requestComplete)
|
||||
_eos = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return actuallyRead;
|
||||
}
|
||||
|
||||
bool NetworkReadStreamEmscripten::hasError() const {
|
||||
return !_success;
|
||||
}
|
||||
|
||||
const char *NetworkReadStreamEmscripten::getError() const {
|
||||
return _errorBuffer;
|
||||
}
|
||||
|
||||
long NetworkReadStreamEmscripten::httpResponseCode() const {
|
||||
// return 200;
|
||||
unsigned short responseCode = 0;
|
||||
if (_emscripten_fetch)
|
||||
responseCode = _emscripten_fetch->status;
|
||||
debug(5, "NetworkReadStreamEmscripten::httpResponseCode %hu", responseCode);
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
Common::String NetworkReadStreamEmscripten::currentLocation() const {
|
||||
debug(5, "NetworkReadStreamEmscripten::currentLocation %s", _emscripten_fetch_url);
|
||||
return Common::String(_emscripten_fetch_url);
|
||||
}
|
||||
|
||||
Common::HashMap<Common::String, Common::String> NetworkReadStreamEmscripten::responseHeadersMap() const {
|
||||
|
||||
Common::HashMap<Common::String, Common::String> headers;
|
||||
|
||||
const char *headerString = _responseHeaders.c_str();
|
||||
char **responseHeaders = emscripten_fetch_unpack_response_headers(headerString);
|
||||
assert(responseHeaders);
|
||||
|
||||
int numHeaders = 0;
|
||||
for (; responseHeaders[numHeaders * 2]; ++numHeaders) {
|
||||
// Check both the header and its value are present.
|
||||
assert(responseHeaders[(numHeaders * 2) + 1]);
|
||||
headers[responseHeaders[numHeaders * 2]] = responseHeaders[(numHeaders * 2) + 1];
|
||||
}
|
||||
|
||||
emscripten_fetch_free_unpacked_response_headers(responseHeaders);
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
} // namespace Networking
|
||||
@@ -0,0 +1,82 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_NETWORKREADSTREAMEMSCRIPTEN_H
|
||||
#define BACKENDS_NETWORKING_HTTP_EMSCRIPTEN_NETWORKREADSTREAMEMSCRIPTEN_H
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include <emscripten/fetch.h>
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class NetworkReadStream; // Forward declaration
|
||||
|
||||
class NetworkReadStreamEmscripten : public NetworkReadStream {
|
||||
private:
|
||||
emscripten_fetch_attr_t *_emscripten_fetch_attr;
|
||||
emscripten_fetch_t *_emscripten_fetch;
|
||||
const char *_emscripten_fetch_url = nullptr;
|
||||
char **_emscripten_request_headers;
|
||||
bool _success;
|
||||
char *_errorBuffer;
|
||||
|
||||
void resetStream();
|
||||
void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
|
||||
void setupFormMultipart(const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles);
|
||||
public:
|
||||
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
NetworkReadStreamEmscripten(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval);
|
||||
|
||||
~NetworkReadStreamEmscripten() override;
|
||||
void initEmscripten(const char *url, RequestHeaders *headersList);
|
||||
|
||||
// NetworkReadStream interface
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) override { return false; } // no reuse for Emscripten
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles) override { return false; } // no reuse for Emscripten
|
||||
bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = false) override { return false; } // no reuse for Emscripten
|
||||
|
||||
bool hasError() const override;
|
||||
const char *getError() const override;
|
||||
|
||||
long httpResponseCode() const override;
|
||||
Common::String currentLocation() const override;
|
||||
Common::HashMap<Common::String, Common::String> responseHeadersMap() const override;
|
||||
|
||||
uint32 read(void *dataPtr, uint32 dataSize) override;
|
||||
|
||||
// Static callback functions
|
||||
static void emscriptenOnSuccess(emscripten_fetch_t *fetch);
|
||||
static void emscriptenOnError(emscripten_fetch_t *fetch);
|
||||
static void emscriptenOnProgress(emscripten_fetch_t *fetch);
|
||||
static void emscriptenOnReadyStateChange(emscripten_fetch_t *fetch);
|
||||
void emscriptenDownloadFinished(bool success);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif // EMSCRIPTEN
|
||||
|
||||
#endif
|
||||
190
backends/networking/http/httpjsonrequest.cpp
Normal file
190
backends/networking/http/httpjsonrequest.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/httpjsonrequest.h"
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/formats/json.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
HttpJsonRequest::HttpJsonRequest(JsonCallback cb, ErrorCallback ecb, const Common::String &url) :
|
||||
HttpRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES),
|
||||
_buffer(new byte[HTTP_JSON_REQUEST_BUFFER_SIZE]) {}
|
||||
|
||||
HttpJsonRequest::~HttpJsonRequest() {
|
||||
delete _jsonCallback;
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
void HttpJsonRequest::handle() {
|
||||
if (!_stream) _stream = makeStream();
|
||||
|
||||
if (_stream) {
|
||||
uint32 readBytes = _stream->read(_buffer, HTTP_JSON_REQUEST_BUFFER_SIZE);
|
||||
if (readBytes != 0)
|
||||
if (_contentsStream.write(_buffer, readBytes) != readBytes)
|
||||
warning("HttpJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
|
||||
|
||||
if (_stream->eos()) {
|
||||
char *contents = Common::JSON::zeroTerminateContents(_contentsStream);
|
||||
Common::JSONValue *json = Common::JSON::parse(contents);
|
||||
if (json) {
|
||||
finishJson(json); //it's JSON even if's not 200 OK? That's fine!..
|
||||
} else {
|
||||
if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!..
|
||||
finishJson(nullptr);
|
||||
else
|
||||
finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpJsonRequest::restart() {
|
||||
if (_stream)
|
||||
delete _stream;
|
||||
_stream = nullptr;
|
||||
_contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
|
||||
//with no stream available next handle() will create another one
|
||||
}
|
||||
|
||||
void HttpJsonRequest::finishJson(const Common::JSONValue *json) {
|
||||
Request::finishSuccess();
|
||||
if (_jsonCallback)
|
||||
(*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks!
|
||||
else
|
||||
delete json;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonIsObject(const Common::JSONValue *item, const char *warningPrefix) {
|
||||
if (item == nullptr) {
|
||||
warning("%s: passed item is NULL", warningPrefix);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item->isObject()) return true;
|
||||
|
||||
warning("%s: passed item is not an object", warningPrefix);
|
||||
debug(9, "%s", item->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsObject(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.getVal(key)->isObject()) return true;
|
||||
|
||||
warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key);
|
||||
debug(9, "%s", item.getVal(key)->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsString(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.getVal(key)->isString()) return true;
|
||||
|
||||
warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key);
|
||||
debug(9, "%s", item.getVal(key)->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.getVal(key)->isIntegerNumber()) return true;
|
||||
|
||||
warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key);
|
||||
debug(9, "%s", item.getVal(key)->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsArray(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.getVal(key)->isArray()) return true;
|
||||
|
||||
warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key);
|
||||
debug(9, "%s", item.getVal(key)->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsStringOrIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true;
|
||||
|
||||
warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key);
|
||||
debug(9, "%s", item.getVal(key)->stringify(true).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HttpJsonRequest::jsonContainsAttribute(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
|
||||
if (!item.contains(key)) {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
64
backends/networking/http/httpjsonrequest.h
Normal file
64
backends/networking/http/httpjsonrequest.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_HTTPJSONREQUEST_H
|
||||
#define BACKENDS_NETWORKING_HTTP_HTTPJSONREQUEST_H
|
||||
|
||||
#include "backends/networking/http/httprequest.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/formats/json.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
typedef Response<const Common::JSONValue *> JsonResponse;
|
||||
typedef Common::BaseCallback<const JsonResponse &> *JsonCallback;
|
||||
typedef Common::BaseCallback<const Common::JSONValue *> *JSONValueCallback;
|
||||
|
||||
#define HTTP_JSON_REQUEST_BUFFER_SIZE 512 * 1024
|
||||
|
||||
class HttpJsonRequest: public HttpRequest {
|
||||
protected:
|
||||
JsonCallback _jsonCallback;
|
||||
Common::MemoryWriteStreamDynamic _contentsStream;
|
||||
byte *_buffer;
|
||||
|
||||
/** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */
|
||||
virtual void finishJson(const Common::JSONValue *json);
|
||||
|
||||
public:
|
||||
HttpJsonRequest(JsonCallback cb, ErrorCallback ecb, const Common::String &url);
|
||||
~HttpJsonRequest() override;
|
||||
|
||||
void handle() override;
|
||||
void restart() override;
|
||||
|
||||
static bool jsonIsObject(const Common::JSONValue *item, const char *warningPrefix);
|
||||
static bool jsonContainsObject(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
static bool jsonContainsString(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
static bool jsonContainsIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
static bool jsonContainsArray(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
static bool jsonContainsStringOrIntegerNumber(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
static bool jsonContainsAttribute(const Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
160
backends/networking/http/httprequest.cpp
Normal file
160
backends/networking/http/httprequest.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/httprequest.h"
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
HttpRequest::HttpRequest(DataCallback cb, ErrorCallback ecb, const Common::String &url):
|
||||
Request(cb, ecb), _url(url), _stream(nullptr), _bytesBuffer(nullptr),
|
||||
_bytesBufferSize(0), _uploading(false), _usingPatch(false), _keepAlive(false), _keepAliveIdle(120), _keepAliveInterval(60) {}
|
||||
|
||||
HttpRequest::~HttpRequest() {
|
||||
delete _stream;
|
||||
delete[] _bytesBuffer;
|
||||
}
|
||||
|
||||
NetworkReadStream *HttpRequest::makeStream() {
|
||||
if (_bytesBuffer)
|
||||
return NetworkReadStream::make(_url.c_str(), &_headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true, _keepAlive, _keepAliveIdle, _keepAliveInterval);
|
||||
if (!_formFields.empty() || !_formFiles.empty())
|
||||
return NetworkReadStream::make(_url.c_str(), &_headersList, _formFields, _formFiles, _keepAlive, _keepAliveIdle, _keepAliveInterval);
|
||||
return NetworkReadStream::make(_url.c_str(), &_headersList, _postFields, _uploading, _usingPatch, _keepAlive, _keepAliveIdle, _keepAliveInterval);
|
||||
}
|
||||
|
||||
void HttpRequest::handle() {
|
||||
if (!_stream) _stream = makeStream();
|
||||
|
||||
if (_stream && _stream->eos()) {
|
||||
if (_stream->httpResponseCode() != 200) {
|
||||
warning("HttpRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
|
||||
ErrorResponse error(this, false, true, "HTTP response code is not 200 OK", _stream->httpResponseCode());
|
||||
finishError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return)
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::restart() {
|
||||
if (_stream)
|
||||
delete _stream;
|
||||
_stream = nullptr;
|
||||
//with no stream available next handle() will create another one
|
||||
}
|
||||
|
||||
Common::String HttpRequest::date() const {
|
||||
if (_stream) {
|
||||
Common::HashMap<Common::String, Common::String> headers = _stream->responseHeadersMap();
|
||||
if (headers.contains("date"))
|
||||
return headers["date"];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void HttpRequest::setHeaders(const Common::Array<Common::String> &headers) {
|
||||
_headersList = headers;
|
||||
}
|
||||
|
||||
void HttpRequest::addHeader(const Common::String &header) {
|
||||
_headersList.push_back(header);
|
||||
}
|
||||
|
||||
void HttpRequest::addPostField(const Common::String &keyValuePair) {
|
||||
if (_bytesBuffer)
|
||||
warning("HttpRequest: added POST fields would be ignored, because there is buffer present");
|
||||
|
||||
if (!_formFields.empty() || !_formFiles.empty())
|
||||
warning("HttpRequest: added POST fields would be ignored, because there are form fields/files present");
|
||||
|
||||
if (_postFields == "")
|
||||
_postFields = keyValuePair;
|
||||
else
|
||||
_postFields += "&" + keyValuePair;
|
||||
}
|
||||
|
||||
void HttpRequest::addFormField(const Common::String &name, const Common::String &value) {
|
||||
if (_bytesBuffer)
|
||||
warning("HttpRequest: added POST form fields would be ignored, because there is buffer present");
|
||||
|
||||
if (_formFields.contains(name))
|
||||
warning("HttpRequest: form field '%s' already had a value", name.c_str());
|
||||
|
||||
_formFields[name] = value;
|
||||
}
|
||||
|
||||
void HttpRequest::addFormFile(const Common::String &name, const Common::Path &filename) {
|
||||
if (_bytesBuffer)
|
||||
warning("HttpRequest: added POST form files would be ignored, because there is buffer present");
|
||||
|
||||
if (_formFields.contains(name))
|
||||
warning("HttpRequest: form file field '%s' already had a value", name.c_str());
|
||||
|
||||
_formFiles[name] = filename;
|
||||
}
|
||||
|
||||
void HttpRequest::setBuffer(byte *buffer, uint32 size) {
|
||||
if (_postFields != "")
|
||||
warning("HttpRequest: added POST fields would be ignored, because buffer added");
|
||||
|
||||
if (_bytesBuffer)
|
||||
delete[] _bytesBuffer;
|
||||
|
||||
_bytesBuffer = buffer;
|
||||
_bytesBufferSize = size;
|
||||
}
|
||||
|
||||
void HttpRequest::usePut() { _uploading = true; }
|
||||
|
||||
void HttpRequest::usePatch() { _usingPatch = true; }
|
||||
|
||||
void HttpRequest::connectionKeepAlive(long idle, long interval) {
|
||||
_keepAlive = true;
|
||||
_keepAliveIdle = idle;
|
||||
_keepAliveInterval = interval;
|
||||
}
|
||||
|
||||
void HttpRequest::connectionClose() {
|
||||
_keepAlive = false;
|
||||
}
|
||||
|
||||
NetworkReadStreamResponse HttpRequest::execute() {
|
||||
if (!_stream) {
|
||||
_stream = makeStream();
|
||||
ConnMan.addRequest(this);
|
||||
}
|
||||
|
||||
return NetworkReadStreamResponse(this, _stream);
|
||||
}
|
||||
|
||||
const NetworkReadStream *HttpRequest::getNetworkReadStream() const { return _stream; }
|
||||
|
||||
void HttpRequest::wait(int spinlockDelay) {
|
||||
while (state() == Networking::PROCESSING) {
|
||||
g_system->delayMillis(spinlockDelay);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
110
backends/networking/http/httprequest.h
Normal file
110
backends/networking/http/httprequest.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_HTTPREQUEST_H
|
||||
#define BACKENDS_NETWORKING_HTTP_HTTPREQUEST_H
|
||||
|
||||
#include "backends/networking/http/request.h"
|
||||
#include "common/path.h"
|
||||
#include "common/str.h"
|
||||
#include "common/array.h"
|
||||
#include "common/list.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/hash-str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
typedef Common::Array<Common::String> RequestHeaders;
|
||||
|
||||
class NetworkReadStream;
|
||||
|
||||
typedef Response<NetworkReadStream *> NetworkReadStreamResponse;
|
||||
typedef Common::BaseCallback<const NetworkReadStreamResponse &> *NetworkReadStreamCallback;
|
||||
|
||||
class HttpRequest: public Request {
|
||||
protected:
|
||||
Common::String _url;
|
||||
NetworkReadStream *_stream;
|
||||
RequestHeaders _headersList;
|
||||
Common::String _postFields;
|
||||
Common::HashMap<Common::String, Common::String> _formFields;
|
||||
Common::HashMap<Common::String, Common::Path> _formFiles;
|
||||
byte *_bytesBuffer;
|
||||
uint32 _bytesBufferSize;
|
||||
bool _uploading; //using PUT method
|
||||
bool _usingPatch; //using PATCH method
|
||||
bool _keepAlive;
|
||||
long _keepAliveIdle, _keepAliveInterval;
|
||||
|
||||
NetworkReadStream *makeStream();
|
||||
|
||||
public:
|
||||
HttpRequest(DataCallback cb, ErrorCallback ecb, const Common::String &url);
|
||||
~HttpRequest() override;
|
||||
|
||||
void handle() override;
|
||||
void restart() override;
|
||||
Common::String date() const override;
|
||||
|
||||
/** Replaces all headers with the passed array of headers. */
|
||||
virtual void setHeaders(const Common::Array<Common::String> &headers);
|
||||
|
||||
/** Adds a header into headers list. */
|
||||
virtual void addHeader(const Common::String &header);
|
||||
|
||||
/** Adds a post field (key=value pair). */
|
||||
virtual void addPostField(const Common::String &field);
|
||||
|
||||
/** Adds a form/multipart field (name, value). */
|
||||
virtual void addFormField(const Common::String &name, const Common::String &value);
|
||||
|
||||
/** Adds a form/multipart file (field name, file name). */
|
||||
virtual void addFormFile(const Common::String &name, const Common::Path &filename);
|
||||
|
||||
/** Sets bytes buffer. */
|
||||
virtual void setBuffer(byte *buffer, uint32 size);
|
||||
|
||||
/** Remembers to use PUT method when it would create NetworkReadStream. */
|
||||
virtual void usePut();
|
||||
|
||||
/** Remembers to use PATCH method when it would create NetworkReadStream. */
|
||||
virtual void usePatch();
|
||||
|
||||
/** Remembers to use Connection: keep-alive or close. */
|
||||
virtual void connectionKeepAlive(long idle = 120, long interval = 60);
|
||||
virtual void connectionClose();
|
||||
|
||||
/**
|
||||
* Starts this Request with ConnMan.
|
||||
* @return its NetworkReadStream in NetworkReadStreamResponse.
|
||||
*/
|
||||
virtual NetworkReadStreamResponse execute();
|
||||
|
||||
/** Returns Request's NetworkReadStream. */
|
||||
const NetworkReadStream *getNetworkReadStream() const;
|
||||
|
||||
/** Waits for Request to be processed. Should be called after Request is put into ConnMan. */
|
||||
void wait(int spinlockDelay = 5);
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
70
backends/networking/http/networkreadstream.cpp
Normal file
70
backends/networking/http/networkreadstream.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "networkreadstream.h"
|
||||
#include "common/tokenizer.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
/*
|
||||
* The make static functions are defined in the implementation-specific subclass
|
||||
*/
|
||||
|
||||
uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
|
||||
uint32 sendSize = _sendingContentsSize - _sendingContentsPos;
|
||||
if (sendSize > maxSize)
|
||||
sendSize = maxSize;
|
||||
for (uint32 i = 0; i < sendSize; ++i) {
|
||||
bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i];
|
||||
}
|
||||
_sendingContentsPos += sendSize;
|
||||
return sendSize;
|
||||
}
|
||||
|
||||
uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 bufferSize) {
|
||||
_responseHeaders += Common::String(buffer, bufferSize);
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
double NetworkReadStream::getProgress() const {
|
||||
if (_progressTotal < 1)
|
||||
return 0;
|
||||
return (double)_progressDownloaded / (double)_progressTotal;
|
||||
}
|
||||
|
||||
void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) {
|
||||
_progressDownloaded = downloaded;
|
||||
_progressTotal = total;
|
||||
}
|
||||
|
||||
uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
|
||||
uint32 actuallyRead = _backingStream.read(dataPtr, dataSize);
|
||||
|
||||
if (actuallyRead == 0) {
|
||||
if (_requestComplete)
|
||||
_eos = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return actuallyRead;
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
162
backends/networking/http/networkreadstream.h
Normal file
162
backends/networking/http/networkreadstream.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_NETWORKREADSTREAM_H
|
||||
#define BACKENDS_NETWORKING_HTTP_NETWORKREADSTREAM_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/hash-str.h"
|
||||
#include "common/hashmap.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/path.h"
|
||||
#include "common/str.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Networking {
|
||||
typedef Common::Array<Common::String> RequestHeaders;
|
||||
|
||||
// Simple interface for platform-specific NetworkReadStream implementations
|
||||
class NetworkReadStream : public Common::ReadStream {
|
||||
protected:
|
||||
Common::MemoryReadWriteStream _backingStream;
|
||||
bool _keepAlive;
|
||||
long _keepAliveIdle, _keepAliveInterval;
|
||||
bool _eos, _requestComplete;
|
||||
const byte *_sendingContentsBuffer;
|
||||
uint32 _sendingContentsSize;
|
||||
uint32 _sendingContentsPos;
|
||||
Common::String _responseHeaders;
|
||||
uint64 _progressDownloaded, _progressTotal;
|
||||
|
||||
/**
|
||||
* Fills the passed buffer with _sendingContentsBuffer contents.
|
||||
* It works similarly to read(), expect it's not for reading
|
||||
* Stream's contents, but for sending our own data to the server.
|
||||
*
|
||||
* @returns how many bytes were actually read (filled in)
|
||||
*/
|
||||
uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize);
|
||||
|
||||
/**
|
||||
* Remembers headers returned to CURL in server's response.
|
||||
*
|
||||
* @returns how many bytes were actually read
|
||||
*/
|
||||
uint32 addResponseHeaders(char *buffer, uint32 bufferSize);
|
||||
|
||||
NetworkReadStream(bool keepAlive, long keepAliveIdle, long keepAliveInterval)
|
||||
: _backingStream(DisposeAfterUse::YES), _eos(false), _requestComplete(false), _sendingContentsBuffer(nullptr),
|
||||
_sendingContentsSize(0), _sendingContentsPos(0), _progressDownloaded(0), _progressTotal(0),
|
||||
_keepAlive(keepAlive), _keepAliveIdle(keepAliveIdle), _keepAliveInterval(keepAliveInterval) {
|
||||
}
|
||||
|
||||
public:
|
||||
/* Implementation-defined Constructors */
|
||||
|
||||
/** Send <postFields>, using POST by default. */
|
||||
static NetworkReadStream *make(const char *url, RequestHeaders *headersList, const Common::String &postFields,
|
||||
bool uploading = false, bool usingPatch = false,
|
||||
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
|
||||
|
||||
/** Send <formFields>, <formFiles>, using POST multipart/form. */
|
||||
static NetworkReadStream *make(const char *url, RequestHeaders *headersList,
|
||||
const Common::HashMap<Common::String, Common::String> &formFields, const Common::HashMap<Common::String, Common::Path> &formFiles,
|
||||
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
|
||||
|
||||
/** Send <buffer>, using POST by default. */
|
||||
static NetworkReadStream *make(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize,
|
||||
bool uploading = false, bool usingPatch = false, bool post = true,
|
||||
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
|
||||
|
||||
/** Send <postFields>, using POST by default. */
|
||||
virtual bool reuse(const char *url, RequestHeaders *headersList, const Common::String &postFields, bool uploading = false, bool usingPatch = false) = 0;
|
||||
/** Send <formFields>, <formFiles>, using POST multipart/form. */
|
||||
virtual bool reuse(
|
||||
const char *url, RequestHeaders *headersList,
|
||||
const Common::HashMap<Common::String, Common::String> &formFields,
|
||||
const Common::HashMap<Common::String, Common::Path> &formFiles) = 0;
|
||||
/** Send <buffer>, using POST by default. */
|
||||
virtual bool reuse(const char *url, RequestHeaders *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true) = 0;
|
||||
/**
|
||||
* Returns true if a read failed because the stream end has been reached.
|
||||
* This flag is cleared by clearErr().
|
||||
* For a SeekableReadStream, it is also cleared by a successful seek.
|
||||
*
|
||||
* @note The semantics of any implementation of this method are
|
||||
* supposed to match those of ISO C feof(). In particular, in a stream
|
||||
* with N bytes, reading exactly N bytes from the start should *not*
|
||||
* set eos; only reading *beyond* the available data should set it.
|
||||
*/
|
||||
bool eos() const override { return _eos; }
|
||||
|
||||
/**
|
||||
* Read data from the stream. Subclasses must implement this
|
||||
* method; all other read methods are implemented using it.
|
||||
*
|
||||
* @note The semantics of any implementation of this method are
|
||||
* supposed to match those of ISO C fread(), in particular where
|
||||
* it concerns setting error and end of file/stream flags.
|
||||
*
|
||||
* @param dataPtr pointer to a buffer into which the data is read
|
||||
* @param dataSize number of bytes to be read
|
||||
* @return the number of bytes which were actually read.
|
||||
*/
|
||||
uint32 read(void *dataPtr, uint32 dataSize) override;
|
||||
|
||||
/**
|
||||
* Returns HTTP response code from inner CURL handle.
|
||||
* It returns -1 to indicate there is no inner handle.
|
||||
*
|
||||
* @note This method should be called when eos() == true.
|
||||
*/
|
||||
virtual long httpResponseCode() const = 0;
|
||||
|
||||
/**
|
||||
* Return current location URL from inner CURL handle.
|
||||
* "" is returned to indicate there is no inner handle.
|
||||
*
|
||||
* @note This method should be called when eos() == true.
|
||||
*/
|
||||
virtual Common::String currentLocation() const = 0;
|
||||
|
||||
/**
|
||||
* Return response headers as HashMap. All header names in
|
||||
* it are lowercase.
|
||||
*
|
||||
* @note This method should be called when eos() == true.
|
||||
*/
|
||||
virtual Common::HashMap<Common::String, Common::String> responseHeadersMap() const = 0;
|
||||
|
||||
/** Returns a number in range [0, 1], where 1 is "complete". */
|
||||
double getProgress() const;
|
||||
|
||||
/** Used in curl progress callback to pass current downloaded/total values. */
|
||||
void setProgress(uint64 downloaded, uint64 total);
|
||||
|
||||
bool keepAlive() const { return _keepAlive; }
|
||||
|
||||
virtual bool hasError() const = 0;
|
||||
virtual const char *getError() const = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
128
backends/networking/http/postrequest.cpp
Normal file
128
backends/networking/http/postrequest.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/postrequest.h"
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
#include "backends/networking/http/httpjsonrequest.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "common/formats/json.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
PostRequest::PostRequest(const Common::String &url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb):
|
||||
Networking::Request(nullptr, ecb), _url(url), _jsonCallback(cb),
|
||||
_workingRequest(nullptr), _ignoreCallback(false), _postData(nullptr), _postLen(0), _jsonData(nullptr) {
|
||||
|
||||
_contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
PostRequest::~PostRequest() {
|
||||
_ignoreCallback = true;
|
||||
if (_workingRequest)
|
||||
_workingRequest->finish();
|
||||
delete _jsonCallback;
|
||||
}
|
||||
|
||||
void PostRequest::setPostData(byte *postData, int postLen) {
|
||||
_postData = postData;
|
||||
_postLen = postLen;
|
||||
|
||||
_contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
void PostRequest::setJSONData(Common::JSONValue *jsonData) {
|
||||
_jsonData = jsonData;
|
||||
|
||||
_contentType = "application/json";
|
||||
}
|
||||
|
||||
void PostRequest::start() {
|
||||
_ignoreCallback = true;
|
||||
if (_workingRequest)
|
||||
_workingRequest->finish();
|
||||
_ignoreCallback = false;
|
||||
|
||||
Networking::JsonCallback innerCallback = new Common::Callback<PostRequest, const Networking::JsonResponse &>(this, &PostRequest::responseCallback);
|
||||
Networking::ErrorCallback errorResponseCallback = new Common::Callback<PostRequest, const Networking::ErrorResponse &>(this, &PostRequest::errorCallback);
|
||||
Networking::HttpJsonRequest *request = new Networking::HttpJsonRequest(innerCallback, errorResponseCallback, _url);
|
||||
|
||||
if (_postData && _jsonData) {
|
||||
warning("Error, both data and JSON present while calling %s", _url.c_str());
|
||||
|
||||
_jsonData = nullptr;
|
||||
}
|
||||
|
||||
request->addHeader(Common::String::format("Content-Type: %s", _contentType.c_str()));
|
||||
|
||||
if (_postData)
|
||||
request->setBuffer(_postData, _postLen);
|
||||
|
||||
|
||||
if (_jsonData)
|
||||
request->addPostField(Common::JSON::stringify(_jsonData));
|
||||
|
||||
_workingRequest = ConnMan.addRequest(request);
|
||||
}
|
||||
|
||||
void PostRequest::responseCallback(const Networking::JsonResponse &response) {
|
||||
const Common::JSONValue *json = response.value;
|
||||
_workingRequest = nullptr;
|
||||
if (_ignoreCallback) {
|
||||
delete json;
|
||||
return;
|
||||
}
|
||||
if (response.request) _date = response.request->date();
|
||||
|
||||
Networking::ErrorResponse error(this, "PostRequest::responseCallback: unknown error");
|
||||
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
|
||||
if (rq && rq->getNetworkReadStream())
|
||||
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
|
||||
|
||||
if (json == nullptr) {
|
||||
error.response = "Failed to parse JSON, null passed!";
|
||||
finishError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
finishSuccess();
|
||||
|
||||
if (_jsonCallback)
|
||||
(*_jsonCallback)(json);
|
||||
|
||||
delete json;
|
||||
}
|
||||
|
||||
void PostRequest::errorCallback(const Networking::ErrorResponse &error) {
|
||||
_workingRequest = nullptr;
|
||||
if (_ignoreCallback)
|
||||
return;
|
||||
if (error.request)
|
||||
_date = error.request->date();
|
||||
finishError(error);
|
||||
}
|
||||
|
||||
void PostRequest::handle() {}
|
||||
|
||||
void PostRequest::restart() { start(); }
|
||||
|
||||
Common::String PostRequest::date() const { return _date; }
|
||||
|
||||
} // End of namespace Networking
|
||||
63
backends/networking/http/postrequest.h
Normal file
63
backends/networking/http/postrequest.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_POSTREQUEST_H
|
||||
#define BACKENDS_NETWORKING_HTTP_POSTREQUEST_H
|
||||
|
||||
#include "backends/networking/http/request.h"
|
||||
#include "backends/networking/http/httpjsonrequest.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class PostRequest : public Networking::Request {
|
||||
Common::String _url;
|
||||
Networking::JSONValueCallback _jsonCallback;
|
||||
Request *_workingRequest;
|
||||
bool _ignoreCallback;
|
||||
Common::String _date;
|
||||
|
||||
byte *_postData;
|
||||
int _postLen;
|
||||
Common::JSONValue *_jsonData;
|
||||
|
||||
Common::String _contentType;
|
||||
|
||||
void responseCallback(const Networking::JsonResponse &response);
|
||||
void errorCallback(const Networking::ErrorResponse &error);
|
||||
|
||||
public:
|
||||
PostRequest(const Common::String &url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb);
|
||||
~PostRequest() override;
|
||||
|
||||
void start();
|
||||
|
||||
void setPostData(byte *postData, int postLen);
|
||||
void setJSONData(Common::JSONValue *jsonData);
|
||||
void setContentType(const Common::String &type) { _contentType = type; }
|
||||
|
||||
void handle() override;
|
||||
void restart() override;
|
||||
Common::String date() const override;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
73
backends/networking/http/request.cpp
Normal file
73
backends/networking/http/request.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/request.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
ErrorResponse::ErrorResponse(Request *rq, const Common::String &resp):
|
||||
request(rq), interrupted(false), failed(true), response(resp), httpResponseCode(-1) {}
|
||||
|
||||
ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, const Common::String &resp, long httpCode):
|
||||
request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {}
|
||||
|
||||
Request::Request(DataCallback cb, ErrorCallback ecb):
|
||||
_callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {}
|
||||
|
||||
Request::~Request() {
|
||||
delete _callback;
|
||||
delete _errorCallback;
|
||||
}
|
||||
|
||||
void Request::handleRetry() {
|
||||
if (_retryInSeconds > 0) {
|
||||
--_retryInSeconds;
|
||||
} else {
|
||||
_state = PROCESSING;
|
||||
restart();
|
||||
}
|
||||
}
|
||||
|
||||
void Request::pause() { _state = PAUSED; }
|
||||
|
||||
void Request::finish() {
|
||||
ErrorResponse error(this, true, false, "Request::finish() was called (i.e. interrupted)", -1);
|
||||
finishError(error);
|
||||
}
|
||||
|
||||
void Request::retry(uint32 seconds) {
|
||||
_state = RETRY;
|
||||
_retryInSeconds = seconds;
|
||||
}
|
||||
|
||||
RequestState Request::state() const { return _state; }
|
||||
|
||||
Common::String Request::date() const { return ""; }
|
||||
|
||||
void Request::finishError(const ErrorResponse &error, RequestState state) {
|
||||
_state = state;
|
||||
if (_errorCallback)
|
||||
(*_errorCallback)(error);
|
||||
}
|
||||
|
||||
void Request::finishSuccess() { _state = FINISHED; }
|
||||
|
||||
} // End of namespace Networking
|
||||
202
backends/networking/http/request.h
Normal file
202
backends/networking/http/request.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_REQUEST_H
|
||||
#define BACKENDS_NETWORKING_HTTP_REQUEST_H
|
||||
|
||||
#include "common/callback.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class Request;
|
||||
|
||||
/**
|
||||
* Response<T> is a struct to be returned from Request
|
||||
* to user's callbacks. It's a type safe way to indicate
|
||||
* which "return value" Request has and user awaits.
|
||||
*
|
||||
* It just keeps a Request pointer together with
|
||||
* some T value (which might be a pointer, a reference
|
||||
* or a plain type (copied by value)).
|
||||
*
|
||||
* To make it more convenient, typedefs are used.
|
||||
* For example, Response<void *> is called DataResponse
|
||||
* and corresponding callback pointer is DataCallback.
|
||||
*/
|
||||
|
||||
template<typename T> struct Response {
|
||||
const Request *request;
|
||||
T value;
|
||||
|
||||
Response(const Request *rq, T v) : request(rq), value(v) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* ErrorResponse is a struct to be returned from Request
|
||||
* to user's failure callbacks.
|
||||
*
|
||||
* It keeps a Request pointer together with some useful
|
||||
* information fields, which would explain why failure
|
||||
* callback was called.
|
||||
*
|
||||
* <interrupted> flag is set when Request was interrupted,
|
||||
* i.e. finished by user with finish() call.
|
||||
*
|
||||
* <failed> flag is set when Request has failed because of
|
||||
* some error (bad server response, for example).
|
||||
*
|
||||
* <response> contains server's original response.
|
||||
*
|
||||
* <httpResponseCode> contains server's HTTP response code.
|
||||
*/
|
||||
|
||||
struct ErrorResponse {
|
||||
Request *request;
|
||||
bool interrupted;
|
||||
bool failed;
|
||||
Common::String response;
|
||||
long httpResponseCode;
|
||||
|
||||
ErrorResponse(Request *rq, const Common::String &resp);
|
||||
ErrorResponse(Request *rq, bool interrupt, bool failure, const Common::String &resp, long httpCode);
|
||||
};
|
||||
|
||||
typedef Response<void *> DataResponse;
|
||||
typedef Common::BaseCallback<const DataResponse &> *DataCallback;
|
||||
typedef Common::BaseCallback<const ErrorResponse &> *ErrorCallback;
|
||||
|
||||
/**
|
||||
* RequestState is used to indicate current Request state.
|
||||
* ConnectionManager uses it to decide what to do with the Request.
|
||||
*
|
||||
* PROCESSING state indicates that Request is working.
|
||||
* ConnectionManager calls handle() method of Requests in that state.
|
||||
*
|
||||
* PAUSED state indicates that Request is not working.
|
||||
* ConnectionManager keeps Requests in that state and doesn't call any methods of those.
|
||||
*
|
||||
* RETRY state indicates that Request must restart after a few seconds.
|
||||
* ConnectionManager calls handleRetry() method of Requests in that state.
|
||||
* Default handleRetry() implementation decreases _retryInSeconds value
|
||||
* until it reaches zero. When it does, Request's restart() method is called.
|
||||
*
|
||||
* FINISHED state indicates that Request did the work and might be deleted.
|
||||
* ConnectionManager deletes Requests in that state.
|
||||
* After this state is set, but before ConnectionManager deletes the Request,
|
||||
* Request calls user's callback. User can ask Request to change its state
|
||||
* by calling retry() or pause() methods and Request won't be deleted.
|
||||
*
|
||||
* Request get a success and failure callbacks. Request must call one
|
||||
* (and only one!) of these callbacks when it sets FINISHED state.
|
||||
*/
|
||||
enum RequestState {
|
||||
PROCESSING,
|
||||
PAUSED,
|
||||
RETRY,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
class Request {
|
||||
protected:
|
||||
/**
|
||||
* Callback, which should be called when Request is finished.
|
||||
* That's the way Requests pass the result to the code which asked to create this request.
|
||||
*
|
||||
* @note some Requests use their own callbacks to return something but void *.
|
||||
* @note callback must be called in finish() or similar method.
|
||||
*/
|
||||
DataCallback _callback;
|
||||
|
||||
/**
|
||||
* Callback, which should be called when Request is failed/interrupted.
|
||||
* That's the way Requests pass error information to the code which asked to create this request.
|
||||
* @note callback must be called in finish() or similar method.
|
||||
*/
|
||||
ErrorCallback _errorCallback;
|
||||
|
||||
/**
|
||||
* Request state, which is used by ConnectionManager to determine
|
||||
* whether request might be deleted or it's still working.
|
||||
*
|
||||
* State might be changed from outside with finish(), pause() or
|
||||
* retry() methods. Override these if you want to react to these
|
||||
* changes correctly.
|
||||
*/
|
||||
RequestState _state;
|
||||
|
||||
/** In RETRY state this indicates whether it's time to call restart(). */
|
||||
uint32 _retryInSeconds;
|
||||
|
||||
/** Sets FINISHED state and calls the _errorCallback with given error. */
|
||||
virtual void finishError(const ErrorResponse &error, RequestState state = FINISHED);
|
||||
|
||||
/** Sets FINISHED state. Implementations might extend it if needed. */
|
||||
virtual void finishSuccess();
|
||||
|
||||
public:
|
||||
Request(DataCallback cb, ErrorCallback ecb);
|
||||
virtual ~Request();
|
||||
|
||||
/** Method, which does actual work. Depends on what this Request is doing. */
|
||||
virtual void handle() = 0;
|
||||
|
||||
/** Method, which is called by ConnectionManager when Request's state is RETRY. */
|
||||
virtual void handleRetry();
|
||||
|
||||
/** Method, which is used to restart the Request. */
|
||||
virtual void restart() = 0;
|
||||
|
||||
/** Method, which is called to pause the Request. */
|
||||
virtual void pause();
|
||||
|
||||
/**
|
||||
* Method, which is called to *interrupt* the Request.
|
||||
* When it's called, Request must stop its work and
|
||||
* call the failure callback to notify user.
|
||||
*/
|
||||
virtual void finish();
|
||||
|
||||
/** Method, which is called to retry the Request. */
|
||||
virtual void retry(uint32 seconds);
|
||||
|
||||
/** Returns Request's current state. */
|
||||
RequestState state() const;
|
||||
|
||||
/**
|
||||
* Return date this Request received from server.
|
||||
* It could be extracted from "Date" header,
|
||||
* which is kept in NetworkReadStream.
|
||||
*
|
||||
* @note not all Requests do that, so "" is returned
|
||||
* to indicate the date is unknown. That's also true
|
||||
* if no server response available or no "Date" header
|
||||
* was passed.
|
||||
*
|
||||
* @returns date from "Date" response header.
|
||||
*/
|
||||
virtual Common::String date() const;
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
88
backends/networking/http/session.cpp
Normal file
88
backends/networking/http/session.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/session.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
Session::Session(const Common::String &prefix):
|
||||
_prefix(prefix), _request(nullptr) {}
|
||||
|
||||
Session::~Session() {
|
||||
close();
|
||||
}
|
||||
|
||||
static Common::String constructUrl(const Common::String &prefix, const Common::String &url) {
|
||||
// check url prefix
|
||||
if (!prefix.empty()) {
|
||||
if (url.contains("://")) {
|
||||
if (!url.hasPrefix(prefix)) {
|
||||
warning("Session: given URL does not match the prefix!\n\t%s\n\t%s", url.c_str(), prefix.c_str());
|
||||
return Common::String();
|
||||
}
|
||||
} else {
|
||||
// if no schema given, just append <url> to <_prefix>
|
||||
Common::String newUrl = prefix;
|
||||
if (newUrl.lastChar() != '/' && (url.size() > 0 && url.firstChar() != '/'))
|
||||
newUrl += "/";
|
||||
newUrl += url;
|
||||
return newUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
SessionRequest *Session::get(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary) {
|
||||
Common::String builtUrl = constructUrl(_prefix, url);
|
||||
|
||||
if (builtUrl.empty())
|
||||
return nullptr;
|
||||
|
||||
// check if request has finished (ready to be replaced)
|
||||
if (_request) {
|
||||
if (!_request->complete()) {
|
||||
warning("Session: can't reuse Request that is being processed");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_request) {
|
||||
_request = new SessionRequest(builtUrl, localFile, cb, ecb, binary); // automatically added to ConnMan
|
||||
_request->connectionKeepAlive();
|
||||
} else {
|
||||
_request->reuse(builtUrl, localFile, cb, ecb, binary);
|
||||
}
|
||||
|
||||
return _request;
|
||||
}
|
||||
|
||||
void Session::close() {
|
||||
if (_request)
|
||||
_request->close();
|
||||
}
|
||||
|
||||
void Session::abortRequest() {
|
||||
if (_request)
|
||||
_request->abortRequest();
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
54
backends/networking/http/session.h
Normal file
54
backends/networking/http/session.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_SESSION_H
|
||||
#define BACKENDS_NETWORKING_HTTP_SESSION_H
|
||||
|
||||
#include "backends/networking/http/sessionrequest.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
class Session {
|
||||
protected:
|
||||
Common::String _prefix;
|
||||
SessionRequest *_request;
|
||||
|
||||
public:
|
||||
Session(const Common::String &prefix = Common::String());
|
||||
~Session();
|
||||
|
||||
SessionRequest *get(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
|
||||
/**
|
||||
* @brief Gracefully close the session
|
||||
*
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Abort session and remove unfinished downloads if they go to local file
|
||||
*
|
||||
*/
|
||||
void abortRequest();
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
260
backends/networking/http/sessionrequest.cpp
Normal file
260
backends/networking/http/sessionrequest.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/networking/http/connectionmanager.h"
|
||||
#include "backends/networking/http/networkreadstream.h"
|
||||
#include "backends/networking/http/sessionrequest.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
#include "common/formats/json.h"
|
||||
|
||||
namespace Networking {
|
||||
|
||||
SessionRequest::SessionRequest(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary):
|
||||
HttpRequest(cb, ecb, url), _contentsStream(DisposeAfterUse::YES),
|
||||
_buffer(new byte[HTTP_SESSION_REQUEST_BUFFER_SIZE]), _text(nullptr), _localFile(nullptr),
|
||||
_started(false), _complete(false), _success(false), _binary(binary) {
|
||||
|
||||
openLocalFile(localFile);
|
||||
|
||||
// automatically go under ConnMan control so nobody would be able to leak the memory
|
||||
// but, we don't need it to be working just yet
|
||||
_state = PAUSED;
|
||||
ConnMan.addRequest(this);
|
||||
}
|
||||
|
||||
SessionRequest::~SessionRequest() {
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
void SessionRequest::openLocalFile(const Common::Path &localFile) {
|
||||
if (localFile.empty())
|
||||
return;
|
||||
|
||||
_localFile = new Common::DumpFile();
|
||||
if (!_localFile->open(localFile, true)) {
|
||||
warning("SessionRequestFile: unable to open file to download into");
|
||||
ErrorResponse error(this, false, true, "SessionRequestFile: unable to open file to download into", -1);
|
||||
finishError(error);
|
||||
delete _localFile;
|
||||
_localFile = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
debug(5, "SessionRequest: opened localfile %s", localFile.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
_binary = true; // Enforce binary
|
||||
}
|
||||
|
||||
bool SessionRequest::reuseStream() {
|
||||
if (!_stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_bytesBuffer)
|
||||
return _stream->reuse(_url.c_str(), &_headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
|
||||
|
||||
if (!_formFields.empty() || !_formFiles.empty())
|
||||
return _stream->reuse(_url.c_str(), &_headersList, _formFields, _formFiles);
|
||||
|
||||
return _stream->reuse(_url.c_str(), &_headersList, _postFields, _uploading, _usingPatch);
|
||||
}
|
||||
|
||||
char *SessionRequest::getPreparedContents() {
|
||||
//write one more byte in the end
|
||||
byte zero[1] = {0};
|
||||
_contentsStream.write(zero, 1);
|
||||
|
||||
//replace all "bad" bytes with '.' character
|
||||
byte *result = _contentsStream.getData();
|
||||
uint32 size = _contentsStream.size();
|
||||
|
||||
//make it zero-terminated string
|
||||
result[size - 1] = '\0';
|
||||
|
||||
return (char *)result;
|
||||
}
|
||||
|
||||
void SessionRequest::finishError(const ErrorResponse &error, RequestState state) {
|
||||
_complete = true;
|
||||
_success = false;
|
||||
HttpRequest::finishError(error, PAUSED);
|
||||
}
|
||||
|
||||
void SessionRequest::finishSuccess() {
|
||||
_state = PAUSED;
|
||||
_complete = true;
|
||||
_success = true;
|
||||
|
||||
if (_localFile) {
|
||||
_localFile->close();
|
||||
delete _localFile;
|
||||
_localFile = nullptr;
|
||||
}
|
||||
|
||||
if (_callback) { // If localfile is present, contentStream is empty, so it is fine
|
||||
_response.buffer = _contentsStream.getData();
|
||||
_response.len = _contentsStream.size();
|
||||
_response.eos = true;
|
||||
|
||||
(*_callback)(DataResponse(this, &_response));
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRequest::start() {
|
||||
if (_state != PAUSED || _started) {
|
||||
warning("Can't start() SessionRequest as it is already started");
|
||||
return;
|
||||
}
|
||||
|
||||
_state = PROCESSING;
|
||||
_started = true;
|
||||
}
|
||||
|
||||
void SessionRequest::startAndWait() {
|
||||
start();
|
||||
wait();
|
||||
}
|
||||
|
||||
void SessionRequest::reuse(const Common::String &url, const Common::Path &localFile, DataCallback cb, ErrorCallback ecb, bool binary) {
|
||||
_url = url;
|
||||
|
||||
delete _callback;
|
||||
delete _errorCallback;
|
||||
_callback = cb;
|
||||
_errorCallback = ecb;
|
||||
|
||||
_binary = binary;
|
||||
|
||||
openLocalFile(localFile);
|
||||
|
||||
restart();
|
||||
}
|
||||
|
||||
void SessionRequest::handle() {
|
||||
// This is called by ConnMan when state is PROCESSING
|
||||
|
||||
if (!_stream) _stream = makeStream();
|
||||
|
||||
if (_stream) {
|
||||
if (_stream->httpResponseCode() != 200 && _stream->httpResponseCode() != 0) {
|
||||
warning("SessionRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
|
||||
ErrorResponse error(this, false, true, "HTTP response code is not 200 OK", _stream->httpResponseCode());
|
||||
finishError(error);
|
||||
return;
|
||||
}
|
||||
uint32 readBytes = _stream->read(_buffer, HTTP_SESSION_REQUEST_BUFFER_SIZE);
|
||||
if (readBytes != 0) {
|
||||
if (!_localFile) {
|
||||
if (_contentsStream.write(_buffer, readBytes) != readBytes)
|
||||
warning("SessionRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
|
||||
} else {
|
||||
_response.buffer = _buffer;
|
||||
_response.len = readBytes;
|
||||
_response.eos = _stream->eos();
|
||||
|
||||
if (_localFile->write(_buffer, readBytes) != readBytes) {
|
||||
warning("DownloadRequest: unable to write all received bytes into output file");
|
||||
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to write all bytes into a file", -1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_callback)
|
||||
(*_callback)(DataResponse(this, &_response));
|
||||
}
|
||||
}
|
||||
|
||||
if (_stream->eos()) {
|
||||
if (_stream->hasError()) {
|
||||
ErrorResponse error(this, false, true, Common::String::format("Stream is in error: %s", _stream->getError()), -1);
|
||||
finishError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
finishSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRequest::restart() {
|
||||
if (_stream) {
|
||||
bool deleteStream = true;
|
||||
if (_keepAlive && reuseStream()) {
|
||||
deleteStream = false;
|
||||
}
|
||||
|
||||
if (deleteStream) {
|
||||
delete _stream;
|
||||
_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
_contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
|
||||
_text = nullptr;
|
||||
_complete = false;
|
||||
_success = false;
|
||||
_started = false;
|
||||
//with no stream available next handle() will create another one
|
||||
}
|
||||
|
||||
void SessionRequest::close() {
|
||||
_state = FINISHED;
|
||||
}
|
||||
|
||||
void SessionRequest::abortRequest() {
|
||||
ErrorResponse error(this, false, true, Common::String::format("Aborted"), -1);
|
||||
finishError(error);
|
||||
|
||||
if (_localFile) {
|
||||
_localFile->close();
|
||||
delete _localFile;
|
||||
_localFile = nullptr;
|
||||
|
||||
// TODO we need to remove file, but there is no API
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionRequest::complete() {
|
||||
return _complete;
|
||||
}
|
||||
|
||||
bool SessionRequest::success() {
|
||||
return _success;
|
||||
}
|
||||
|
||||
char *SessionRequest::text() {
|
||||
if (_binary || _localFile)
|
||||
return nullptr;
|
||||
|
||||
if (_text == nullptr)
|
||||
_text = getPreparedContents();
|
||||
return _text;
|
||||
}
|
||||
|
||||
Common::JSONValue *SessionRequest::json() {
|
||||
if (_binary)
|
||||
error("SessionRequest::json() is called for binary stream");
|
||||
if (_localFile)
|
||||
error("SessionRequest::json() is called for localFile stream");
|
||||
return Common::JSON::parse(text());
|
||||
}
|
||||
|
||||
} // End of namespace Networking
|
||||
103
backends/networking/http/sessionrequest.h
Normal file
103
backends/networking/http/sessionrequest.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_NETWORKING_HTTP_SESSIONREQUEST_H
|
||||
#define BACKENDS_NETWORKING_HTTP_SESSIONREQUEST_H
|
||||
|
||||
#include "backends/networking/http/httprequest.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
namespace Common {
|
||||
class DumpFile;
|
||||
class JSONValue;
|
||||
class Path;
|
||||
}
|
||||
|
||||
namespace Networking {
|
||||
|
||||
#define HTTP_SESSION_REQUEST_BUFFER_SIZE 512 * 1024
|
||||
|
||||
struct SessionFileResponse {
|
||||
byte *buffer;
|
||||
uint32 len;
|
||||
bool eos;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class for reading file and storing locally
|
||||
*
|
||||
* @return Returns SessionFileResponse in the callback
|
||||
*/
|
||||
|
||||
class SessionRequest: public HttpRequest {
|
||||
protected:
|
||||
Common::MemoryWriteStreamDynamic _contentsStream;
|
||||
byte *_buffer;
|
||||
char *_text;
|
||||
bool _started, _complete, _success;
|
||||
bool _binary;
|
||||
Common::DumpFile *_localFile;
|
||||
SessionFileResponse _response;
|
||||
|
||||
bool reuseStream();
|
||||
|
||||
/** Prepares raw bytes from _contentsStream. */
|
||||
char *getPreparedContents();
|
||||
|
||||
void finishError(const ErrorResponse &error, RequestState state = PAUSED) override;
|
||||
void finishSuccess() override;
|
||||
void openLocalFile(const Common::Path &localFile);
|
||||
|
||||
public:
|
||||
SessionRequest(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
|
||||
~SessionRequest() override;
|
||||
|
||||
void start();
|
||||
void startAndWait();
|
||||
|
||||
void reuse(const Common::String &url, const Common::Path &localFile, DataCallback cb = nullptr, ErrorCallback ecb = nullptr, bool binary = false);
|
||||
|
||||
void handle() override;
|
||||
void restart() override;
|
||||
|
||||
/** This request DOES NOT delete automatically after calling callbacks. It gets PAUSED, and in order to make it FINISHED (i.e. delete), this method MUST be called. */
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Closes the current request and removes any unfinished files
|
||||
*
|
||||
*/
|
||||
void abortRequest();
|
||||
|
||||
bool complete();
|
||||
bool success();
|
||||
|
||||
char *text();
|
||||
Common::JSONValue *json();
|
||||
|
||||
byte *getData() { return _contentsStream.getData(); }
|
||||
uint32 getSize() { return _contentsStream.size(); }
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace Networking
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user