void OneDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
	_workingRequest = nullptr;
	if (_ignoreCallback)
		return;

	Networking::ErrorResponse error(this, false, true, "", -1);
	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
	if (rq && rq->getNetworkReadStream())
		error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

	Common::JSONValue *json = response.value;
	if (json == nullptr) {
		error.response = "Failed to parse JSON, null passed!";
		finishError(error);
		return;
	}

	if (json->isObject()) {
		Common::JSONObject object = json->asObject();

		if (object.contains("error")) {
			warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str());
			error.response = json->stringify(true);
			finishError(error);
			delete json;
			return;
		}

		if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") &&
			Networking::CurlJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") &&
			Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") &&
			Networking::CurlJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) {
			//finished
			Common::String path = _savePath;
			uint32 size = object.getVal("size")->asIntegerNumber();
			uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString());
			finishUpload(StorageFile(path, size, timestamp, false));
			return;
		}

		if (_uploadUrl == "") {
			if (Networking::CurlJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest"))
				_uploadUrl = object.getVal("uploadUrl")->asString();
		}
	}

	if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
		warning("OneDriveUploadRequest: no file info to return");
		finishUpload(StorageFile(_savePath, 0, 0, false));
	} else {
		uploadNextPart();
	}

	delete json;
}
void GoogleDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
	_workingRequest = nullptr;
	if (_ignoreCallback)
		return;

	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)error.request;
	if (rq) {
		const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
		if (stream) {
			long code = stream->httpResponseCode();
			if (code == 308 && handleHttp308(stream)) {
				return;
			}
		}
	}

	finishError(error);
}
void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse response) {
	_workingRequest = nullptr;
	if (_ignoreCallback)
		return;

	Networking::ErrorResponse error(this, false, true, "", -1);
	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
	if (rq) {
		const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
		if (stream) {
			long code = stream->httpResponseCode();
			Common::String headers = stream->responseHeaders();
			if (code == 200) {
				const char *cstr = headers.c_str();
				const char *position = strstr(cstr, "Location: ");

				if (position) {
					Common::String result = "";
					char c;
					for (const char *i = position + 10; c = *i, c != 0; ++i) {
						if (c == '\n' || c == '\r')
							break;
						result += c;
					}
					_uploadUrl = result;
					uploadNextPart();
					return;
				}
			}

			error.httpResponseCode = code;
		}
	}

	Common::JSONValue *json = response.value;
	delete json;

	finishError(error);
}
void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonResponse response) {
	_workingRequest = nullptr;
	Common::JSONValue *json = response.value;

	if (_ignoreCallback) {
		delete json;
		return;
	}

	if (response.request)
		_date = response.request->date();

	Networking::ErrorResponse error(this);
	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)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;
	}

	if (!json->isObject()) {
		error.response = "Passed JSON is not an object!";
		finishError(error);
		delete json;
		return;
	}

	Common::JSONObject object = json->asObject();

	//check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
	if (!Networking::CurlJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) {
		error.response = "\"value\" not found or that's not an array!";
		finishError(error);
		delete json;
		return;
	}

	Common::JSONArray items = object.getVal("value")->asArray();
	for (uint32 i = 0; i < items.size(); ++i) {
		if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue;

		Common::JSONObject item = items[i]->asObject();

		if (!Networking::CurlJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue;
		if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue;
		if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue;
		if (!Networking::CurlJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue;

		Common::String path = _currentDirectory + item.getVal("name")->asString();
		bool isDirectory = item.contains("folder");
		uint32 size = item.getVal("size")->asIntegerNumber();
		uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString());

		StorageFile file(path, size, timestamp, isDirectory);
		_files.push_back(file);
		if (_requestedRecursive && file.isDirectory()) {
			_directoriesQueue.push_back(file.path());
		}
	}

	bool hasMore = object.contains("@odata.nextLink");
	if (hasMore) {
		if (!Networking::CurlJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) {
			error.response = "\"@odata.nextLink\" is not a string!";
			finishError(error);
			delete json;
			return;
		}

		makeRequest(object.getVal("@odata.nextLink")->asString());
	} else {
		listNextDirectory();
	}

	delete json;
}
void GoogleDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
	_workingRequest = nullptr;
	if (_ignoreCallback)
		return;

	Networking::ErrorResponse error(this, false, true, "", -1);
	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
	if (rq) {
		const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
		if (stream) {
			long code = stream->httpResponseCode();
			error.httpResponseCode = code;
			if (code == 308 && handleHttp308(stream)) {
				delete (Common::JSONValue *)response.value;
				return;
			}
		}
	}

	Common::JSONValue *json = response.value;
	if (json == nullptr) {
		error.response = "Failed to parse JSON, null passed!";
		finishError(error);
		return;
	}

	if (json->isObject()) {
		Common::JSONObject object = json->asObject();

		if (object.contains("error")) {
			warning("GoogleDrive returned error: %s", json->stringify(true).c_str());
			error.response = json->stringify(true);
			finishError(error);
			delete json;
			return;
		}

		if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") &&
			Networking::CurlJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") &&
			Networking::CurlJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) {
			//finished
			Common::String id = object.getVal("id")->asString();
			Common::String name = object.getVal("name")->asString();
			bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
			uint32 size = 0, timestamp = 0;
			if (Networking::CurlJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true))
				size = object.getVal("size")->asString().asUint64();
			if (Networking::CurlJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true))
				timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString());

			finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
			return;
		}
	}

	if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
		warning("GoogleDriveUploadRequest: no file info to return");
		finishUpload(StorageFile(_savePath, 0, 0, false));
	} else {
		uploadNextPart();
	}

	delete json;
}