cpprestsdk/Release/samples/CasaLens/datafetcher.cpp

455 строки
20 KiB
C++

/***
* Copyright (C) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
*
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* datafetcher.cpp: Listener code: Given a location/postal code, the listener queries different services
* for weather, things to do: events, movie and pictures and returns it to the client.
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#include "stdafx.h"
#include "casalens.h"
using namespace utility;
using namespace concurrency;
using namespace concurrency::streams;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::http::experimental;
using namespace web::http::experimental::listener;
// In case of any failure to fetch data from a service,
// create a json object with "error" key and value containing the exception details.
pplx::task<json::value> CasaLens::handle_exception(pplx::task<json::value>& t, const utility::string_t& field_name)
{
try
{
t.get();
}
catch (const std::exception& ex)
{
json::value error_json = json::value::object();
error_json[field_name] = json::value::object();
error_json[field_name][error_json_key] = json::value::string(utility::conversions::to_string_t(ex.what()));
return pplx::task_from_result<json::value>(error_json);
}
return t;
}
// Given postal code, query eventful service and obtain num_events upcoming events at that location.
// Returns a task of json::value of event data
// JSON result Format:
// {"events":["title":"event1","url":"http://event1","starttime":"ddmmyy",..]}
pplx::task<json::value> CasaLens::get_events(const utility::string_t& postal_code)
{
uri_builder ub(casalens_creds::events_url + postal_code);
ub.append_query(casalens_creds::events_keyname, casalens_creds::events_key);
auto event_url = ub.to_string();
http_client ev_client(event_url);
return ev_client.request(methods::GET, event_url)
.then([](http_response resp) { return resp.extract_json(); })
.then([](json::value event_json) {
json::value event_result_node = json::value::object();
if (!event_json[U("events")][U("event")].is_null())
{
event_result_node[U("events")] = json::value::array();
int i = 0;
for (auto& iter : event_json[U("events")][U("event")].as_array())
{
const auto& event = iter;
auto iTitle = event.as_object().find(U("title"));
if (iTitle == event.as_object().end())
{
throw web::json::json_exception(U("title key not found"));
}
event_result_node[events_json_key][i][U("title")] = iTitle->second;
auto iUrl = event.as_object().find(U("url"));
if (iUrl == event.as_object().end())
{
throw web::json::json_exception(U("url key not found"));
}
event_result_node[events_json_key][i][U("url")] = iUrl->second;
auto iStartTime = event.as_object().find(U("start_time"));
if (iStartTime == event.as_object().end())
{
throw web::json::json_exception(U("start_time key not found"));
}
event_result_node[events_json_key][i][U("starttime")] = iStartTime->second;
auto iDescription = event.as_object().find(U("description"));
if (iDescription == event.as_object().end())
{
throw web::json::json_exception(U("descriotion key not found"));
}
event_result_node[events_json_key][i][U("description")] = iDescription->second;
auto iVenueAddress = event.as_object().find(U("venue_address"));
if (iVenueAddress == event.as_object().end())
{
throw web::json::json_exception(U("venue_address key not found"));
}
auto iCityName = event.as_object().find(U("city_name"));
if (iCityName == event.as_object().end())
{
throw web::json::json_exception(U("city_name key not found"));
}
event_result_node[events_json_key][i][U("venue_address")] =
json::value::string(iVenueAddress->second.as_string() + U(" ") + iCityName->second.as_string());
if (i++ > num_events) break;
}
}
else
{
// Event data is null, we hit an error.
event_result_node[events_json_key] = json::value::object();
event_result_node[events_json_key][error_json_key] = event_json[U("events")][U("description")];
}
return event_result_node;
})
.then([=](pplx::task<json::value> t) { return handle_exception(t, events_json_key); });
}
// Query openweathermap API and obtain weather information at the given location.
// Returns a task of json::value containing the weather data.
// Format:
// {"weather":{"temperature":"49","pressure":"xxx"..}}
pplx::task<json::value> CasaLens::get_weather(const utility::string_t& postal_code)
{
utility::string_t weather_url(casalens_creds::weather_url);
uri_builder ub_weather(weather_url.append(postal_code));
ub_weather.append_query(U("units"), U("imperial"));
http_client weather_client(ub_weather.to_string());
return weather_client.request(methods::GET)
.then([](http_response resp) { return resp.extract_string(); })
.then([](utility::string_t weather_str) {
json::value weather_json = json::value::parse(weather_str);
auto& j = weather_json[U("list")][0][U("main")];
json::value weather_result_node = json::value::object();
weather_result_node[weather_json_key] = json::value::object();
auto& w = weather_result_node[U("weather")];
w[U("temperature")] = j[U("temp")];
w[U("pressure")] = j[U("pressure")];
w[U("temp_min")] = j[U("temp_min")];
w[U("temp_max")] = j[U("temp_max")];
w[U("image")] =
json::value::string(U("http://openweathermap.org/img/w/") +
weather_json[U("list")][0][U("weather")][0][U("icon")].as_string() + U(".png"));
w[U("description")] = weather_json[U("list")][0][U("weather")][0][U("description")];
return weather_result_node;
})
.then([=](pplx::task<json::value> t) { return handle_exception(t, weather_json_key); });
}
// Query bing images to fetch some image URLs of the given location
// Returns a task of json::Value
// JSON result format:
// {"images":["url1", "url2"]}
pplx::task<json::value> CasaLens::get_pictures(const utility::string_t& location, const utility::string_t& count)
{
http_client_config config;
credentials cred(casalens_creds::images_keyname, casalens_creds::images_key);
config.set_credentials(cred);
utility::string_t bing_url(casalens_creds::images_url);
uri_builder ub_bing(bing_url);
ub_bing.append_query(U("Query"), U("'") + location + U("'"));
ub_bing.append_query(U("$top"), count);
ub_bing.append_query(U("ImageFilters"), U("'Size:Medium'"));
http_client bing_client(ub_bing.to_string(), config);
return bing_client.request(methods::GET)
.then([](http_response resp) { return resp.extract_json(); })
.then([](json::value image_json) {
json::value image_result_node = json::value::object();
image_result_node[images_json_key] = json::value::array();
int i = 0;
for (auto& val : image_json[U("d")][U("results")].as_object())
{
const json::object& image = val.second.as_object();
auto iMediaUrl = image.find(U("MediaUrl"));
if (iMediaUrl == image.end())
{
throw web::json::json_exception(U("MediaUrl key not found"));
}
image_result_node[images_json_key][i] = iMediaUrl->second;
if (i++ > num_images) break;
}
return image_result_node;
})
.then([=](pplx::task<json::value> t) { return handle_exception(t, images_json_key); });
}
// Get the current date
std::wstring CasaLens::get_date()
{
time_t t = time(0);
struct tm now;
std::wostringstream date;
if (0 == localtime_s(&now, &t))
{
date << (now.tm_year + 1900) << '-' << (now.tm_mon + 1) << '-' << now.tm_mday;
}
return date.str();
}
// Query tmsapi and fetch current movie showtimes at local theaters, for the given postal code
// Also quert bing images for movie posters
// Returns a task of JSON value
// JSON result format:
// "movies":[{"title":"abc","theatre":[{"name":"theater1","datetime":["dd-mm-yyThh:mm"]},{"name":"theater2","datetime":["ddmmyy"]}],"poster":"img-url"}}]}..
pplx::task<json::value> CasaLens::get_movies(const utility::string_t& postal_code)
{
uri_builder ub_movie(casalens_creds::movies_url);
ub_movie.append_query(U("startDate"), get_date());
ub_movie.append_query(U("zip"), postal_code);
ub_movie.append_query(casalens_creds::movies_keyname, casalens_creds::movies_key);
ub_movie.append_query(U("imageSize"), U("Sm"));
http_client tms_client(ub_movie.to_string());
return tms_client.request(methods::GET)
.then([](http_response resp) { return resp.extract_json(); })
.then([](json::value movie_json) {
// From the data obtained from tmsapi, construct movie JSON value to be sent to the client
// We will collect data for 5 movies, and 5 showtime info per movie.
json::value movie_result_node = json::value::object();
if (movie_json.size())
{
auto temp = json::value::array();
int i = 0;
for (auto iter = movie_json.as_array().cbegin(); iter != movie_json.as_array().cend() && i < num_movies;
iter++, i++)
{
auto iShowTimes = iter->as_object().find(U("showtimes"));
if (iShowTimes == iter->as_object().end())
{
throw web::json::json_exception(U("showtimes key not found"));
}
auto& showtimes = iShowTimes->second;
int j = 0;
int showtime_index = 0;
int theater_index = -1;
utility::string_t current_theater;
auto iTitle = iter->as_object().find(U("title"));
if (iTitle == iter->as_object().end())
{
throw web::json::json_exception(U("title key not found"));
}
temp[i][U("title")] = iTitle->second;
for (auto iter2 = showtimes.as_array().cbegin();
iter2 != showtimes.as_array().cend() && j < num_movies;
iter2++, j++)
{
auto iTheatre = iter2->as_object().find(U("theatre"));
if (iTheatre == iter2->as_object().end())
{
throw web::json::json_exception(U("theatre key not found"));
}
auto iName = iTheatre->second.as_object().find(U("name"));
if (iName == iTheatre->second.as_object().end())
{
throw web::json::json_exception(U("name key not found"));
}
auto theater = iName->second.as_string();
if (0 != theater.compare(current_theater)) // new theater
{
temp[i][U("theatre")][++theater_index][U("name")] =
json::value::string(theater); // Add theater entry
showtime_index = 0;
current_theater = theater;
}
auto iDateTime = iter2->as_object().find(U("dateTime"));
if (iDateTime == iter2->as_object().end())
{
throw web::json::json_exception(U("dateTime key not found"));
}
temp[i][U("theatre")][theater_index][U("datetime")][showtime_index++] =
iDateTime->second; // Update the showtime for same theater
}
}
movie_result_node[movies_json_key] = std::move(temp);
}
else
{
movie_result_node[movies_json_key] = json::value::object();
movie_result_node[movies_json_key][error_json_key] =
json::value::string(U("Failed to fetch movie data"));
}
return pplx::task_from_result(movie_result_node);
})
.then([=](json::value movie_result) {
try
{
// For every movie, obtain movie poster URL from bing
std::vector<utility::string_t> movie_list;
std::vector<pplx::task<json::value>> poster_tasks;
auto date = get_date();
std::wstring year = date.substr(0, date.find(U("-")));
for (auto& iter : movie_result[movies_json_key].as_object())
{
auto title = iter.second[U("title")].as_string();
auto searchStr = title + U(" ") + year + U(" new movie poster");
poster_tasks.push_back(get_pictures(searchStr, U("1")));
movie_list.push_back(title);
}
pplx::when_all(poster_tasks.begin(), poster_tasks.end()).wait();
json::value resp_data = json::value::object();
for (unsigned int i = 0; i < poster_tasks.size(); i++)
{
auto jval = poster_tasks[i].get();
auto poster_url = jval.as_array().begin()[0];
movie_result[movies_json_key][i][U("poster")] = poster_url;
}
}
catch (...)
{
// Ignore any failure in fetching movie posters. Just return
// the movie data to the client.
}
return pplx::task_from_result(movie_result);
})
.then([=](pplx::task<json::value> t) { return handle_exception(t, movies_json_key); });
}
bool CasaLens::is_number(const std::string& s)
{
return !s.empty() &&
std::find_if(s.begin(), s.end(), [](char c) { return !std::isdigit(c, std::locale()); }) == s.end();
}
void CasaLens::fetch_data(http_request message, const std::wstring& postal_code, const std::wstring& location)
{
json::value resp_data;
try
{
m_rwlock.lock_read();
resp_data = m_data[postal_code];
m_rwlock.unlock();
if (resp_data.is_null())
{
std::vector<pplx::task<json::value>> tasks;
tasks.push_back(get_events(postal_code));
tasks.push_back(get_weather(postal_code));
tasks.push_back(get_pictures(location, U("4")));
tasks.push_back(get_movies(postal_code));
pplx::when_all(tasks.begin(), tasks.end()).wait();
resp_data = json::value::object();
for (auto& iter : tasks)
{
auto jval = iter.get();
auto key = jval.as_object().begin()->first;
resp_data[key] = jval.as_object().begin()->second;
}
m_rwlock.lock();
m_data[postal_code] = resp_data;
m_rwlock.unlock();
}
// Reply with the aggregated JSON data
message.reply(status_codes::OK, resp_data).then([](pplx::task<void> t) { handle_error(t); });
}
catch (...)
{
message.reply(status_codes::InternalError).then([](pplx::task<void> t) { handle_error(t); });
}
}
// Check if the input text is a number or string.
// If string => city name, use bing maps API to obtain the postal code for that city
// number => postal code, use google maps API to obtain city name (location data) for that postal code.
// then call fetch_data to query different services, aggregate movie, images, events, weather etc for that city and
// respond to the request.
void CasaLens::get_data(http_request message, const std::wstring& input_text)
{
if (!is_number(utility::conversions::to_utf8string(input_text)))
{
std::wstring bing_maps_url(casalens_creds::bmaps_url);
uri_builder maps_builder;
maps_builder.append_query(U("locality"), input_text);
maps_builder.append_query(casalens_creds::bmaps_keyname, casalens_creds::bmaps_key);
auto s = maps_builder.to_string();
http_client bing_client(bing_maps_url);
bing_client.request(methods::GET, s)
.then([=](http_response resp) { return resp.extract_json(); })
.then([=](json::value maps_result) mutable {
auto coordinates = maps_result[U("resourceSets")][0][U("resources")][0][U("point")];
auto lattitude = coordinates[U("coordinates")][0].serialize();
auto longitude = coordinates[U("coordinates")][1].serialize();
uri_builder ub;
ub.append_path(lattitude + U(",") + longitude)
.append_query(casalens_creds::bmaps_keyname, casalens_creds::bmaps_key);
auto s2 = ub.to_string();
return bing_client.request(methods::GET, s2);
})
.then([](http_response resp) { return resp.extract_json(); })
.then([=](json::value maps_result) {
auto postal_code =
maps_result[U("resourceSets")][0][U("resources")][0][U("address")][U("postalCode")].as_string();
fetch_data(message, postal_code, input_text);
})
.then([=](pplx::task<void> t) {
try
{
t.get();
}
catch (...)
{
message.reply(status_codes::InternalError, U("Failed to fetch the postal code"));
}
});
}
else // get location from postal code
{
http_client client(casalens_creds::gmaps_url);
uri_builder ub;
ub.append_query(U("address"), input_text);
ub.append_query(U("sensor"), U("false"));
client.request(methods::GET, ub.to_string())
.then([](http_response resp) { return resp.extract_json(); })
.then([=](json::value jval) {
auto locationstr = jval[U("results")][0][U("address_components")][1][U("long_name")].as_string();
fetch_data(message, input_text, locationstr);
})
.then([=](pplx::task<void> t) {
try
{
t.get();
}
catch (...)
{
message.reply(status_codes::InternalError, U("Failed to fetch the location from postal code"));
}
});
}
return;
}