From 28691c29e93fbc4fdb5ae4866287282840fba021 Mon Sep 17 00:00:00 2001 From: randomdan Date: Fri, 19 Sep 2014 23:52:30 +0000 Subject: First bash at a slicer ice proxy for TMDb and an untested instantiation in p2pvr's core adapter --- libtmdb/.ycm_extra_conf.py | 114 ++++++++++++ libtmdb/Jamfile.jam | 67 +++++++ libtmdb/conversions.cpp | 35 ++++ libtmdb/httpClient.cpp | 77 ++++++++ libtmdb/httpClient.h | 73 ++++++++ libtmdb/samples/movie_550.json | 57 ++++++ libtmdb/samples/searchMulti_breakingBad.json | 39 ++++ libtmdb/samples/tv_1396.json | 89 +++++++++ libtmdb/testCallMockApi.cpp | 26 +++ libtmdb/testFormatUrls.cpp | 58 ++++++ libtmdb/testModels.cpp | 117 ++++++++++++ libtmdb/tmdb-api.ice | 21 +++ libtmdb/tmdb-common.ice | 90 +++++++++ libtmdb/tmdb-models.ice | 269 +++++++++++++++++++++++++++ libtmdb/tmdb-proxy.cpp | 46 +++++ libtmdb/tmdb-proxy.h | 17 ++ p2pvr/Jamfile.jam | 2 +- p2pvr/daemon/Jamfile.jam | 2 + p2pvr/daemon/daemon.cpp | 20 ++ 19 files changed, 1218 insertions(+), 1 deletion(-) create mode 100644 libtmdb/.ycm_extra_conf.py create mode 100644 libtmdb/Jamfile.jam create mode 100644 libtmdb/conversions.cpp create mode 100644 libtmdb/httpClient.cpp create mode 100644 libtmdb/httpClient.h create mode 100644 libtmdb/samples/movie_550.json create mode 100644 libtmdb/samples/searchMulti_breakingBad.json create mode 100644 libtmdb/samples/tv_1396.json create mode 100644 libtmdb/testCallMockApi.cpp create mode 100644 libtmdb/testFormatUrls.cpp create mode 100644 libtmdb/testModels.cpp create mode 100644 libtmdb/tmdb-api.ice create mode 100644 libtmdb/tmdb-common.ice create mode 100644 libtmdb/tmdb-models.ice create mode 100644 libtmdb/tmdb-proxy.cpp create mode 100644 libtmdb/tmdb-proxy.h diff --git a/libtmdb/.ycm_extra_conf.py b/libtmdb/.ycm_extra_conf.py new file mode 100644 index 0000000..2f379fc --- /dev/null +++ b/libtmdb/.ycm_extra_conf.py @@ -0,0 +1,114 @@ +import os +import ycm_core + +flags = [ +'-Wall', +'-Wextra', +'-Werror', +'-Wc++98-compat', +'-Wno-long-long', +'-Wno-variadic-macros', +'-fexceptions', +'-DNDEBUG', +'-std=c++11', +'-x', +'c++', +'-I', +'.', +'-isystem', +'/usr/include/Ice', +'-isystem', +'/usr/include/slicer', +'-isystem', +'bin/gcc-4.8.3/debug/slicer-yes', +'-isystem', +'/usr/include', +'-isystem', +'/usr/include/glibmm-2.4', +] + +compilation_database_folder = '' + +if os.path.exists( compilation_database_folder ): + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } + diff --git a/libtmdb/Jamfile.jam b/libtmdb/Jamfile.jam new file mode 100644 index 0000000..2e51377 --- /dev/null +++ b/libtmdb/Jamfile.jam @@ -0,0 +1,67 @@ +import testing ; + +alias glibmm : : : : + "`pkg-config --cflags glibmm-2.4`" + "`pkg-config --libs glibmm-2.4`" + ; +lib slicer : : : : /usr/include/slicer ; +lib slicer-json : : : : /usr/include/slicer ; +lib Ice ; +lib IceUtil ; +lib jsonpp ; +lib pthread ; +lib boost_system ; +lib boost_filesystem ; +lib curl ; + +lib tmdb : + [ glob *.cpp *.ice : test*.cpp ] : + Ice + IceUtil + jsonpp + pthread + slicer + slicer-json + boost_system + glibmm + curl + yes + : : + . + Ice + IceUtil + pthread + ; + +unit-test testModels : + [ glob testModels.cpp ] + : + tmdb + slicer + slicer-json + boost_filesystem + boost_system + tmdb + ; + +unit-test testFormatUrls : + [ glob testFormatUrls.cpp ] + : + tmdb + slicer + slicer-json + boost_filesystem + boost_system + tmdb + ; + +unit-test testCallMockApi : + [ glob testCallMockApi.cpp ] + : + tmdb + slicer + slicer-json + boost_filesystem + boost_system + tmdb + ; diff --git a/libtmdb/conversions.cpp b/libtmdb/conversions.cpp new file mode 100644 index 0000000..488419f --- /dev/null +++ b/libtmdb/conversions.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#define SHORT(x) boost::numeric_cast< ::Ice::Short >(x) + +namespace Slicer { + std::string + dateToString(const ::TMDb::Date & in) + { + char buf[BUFSIZ]; + struct tm tm({ 0, 0, 0, in.Day, in.Month, in.Year, 0, 0, 0 +#ifdef _BSD_SOURCE + , 0, 0 +#endif + }); + mktime(&tm); + auto len = strftime(buf, BUFSIZ, "%Y-%m-%d", &tm); + return std::string(buf, len); + } + + ::TMDb::Date + stringToDate(const std::string & in) + { + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + auto end = strptime(in.c_str(), "%Y-%m-%d", &tm); + mktime(&tm); + if (!end || *end) { + throw std::runtime_error("Invalid date string: " + in); + } + return { SHORT(1900 + tm.tm_year), SHORT(1 + tm.tm_mon), SHORT(tm.tm_mday) }; + } + +} diff --git a/libtmdb/httpClient.cpp b/libtmdb/httpClient.cpp new file mode 100644 index 0000000..93fdd76 --- /dev/null +++ b/libtmdb/httpClient.cpp @@ -0,0 +1,77 @@ +#include "httpClient.h" +#include +#include +#include +#include + +namespace TMDb { + HttpClient::HttpClient(const std::string & bu, const std::string & k) : + BaseURL(bu), + ApiKey(k) + { + } + + void + HttpClient::packParams(boost::format &) + { + } + + void + HttpClient::appendQueryParameters(std::string & path, const Parameters::value_type & nvp) const + { + path += nvp.first; + path += "="; + auto ev = curl_easy_escape(NULL, nvp.second.value->c_str(), nvp.second.value->size()); + path += ev; + curl_free(ev); + } + + void + HttpClient::appendQueryParameters(std::string & path, const Parameters & parameters) const + { + path += "?"; + appendQueryParameters(path, { "apikey", ApiKey }); + BOOST_FOREACH(const auto & nvp, parameters) { + if (nvp.second.value) { + path += "&"; + appendQueryParameters(path, nvp); + } + } + } + + static size_t + appendString(void * contents, size_t size, size_t nmemb, void * userp) + { + auto data = static_cast(userp); + data->append(static_cast(contents), size * nmemb); + return size * nmemb; + } + + json::Value + HttpClient::FetchJson(const std::string & path) const + { + Glib::ustring jsonData; + + struct curl_slist *headers = NULL; + curl_slist_append(headers, "Accept: application/json"); + + CURL * curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, path.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, appendString); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&jsonData); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers); + CURLcode res = curl_easy_perform(curl_handle); + + long http_code = 0; + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl_handle); + if (res != CURLE_OK) { + throw TMDb::HttpException(http_code); + } + + Glib::ustring::const_iterator itr = jsonData.begin(); + return json::parseValue(itr); + } +} + diff --git a/libtmdb/httpClient.h b/libtmdb/httpClient.h new file mode 100644 index 0000000..1b50b7e --- /dev/null +++ b/libtmdb/httpClient.h @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include + +namespace TMDb { + class HttpClient { + public: + class ParameterValue { + public: + ParameterValue(const std::string & v) : + value(v) + { + } + + template + ParameterValue(const V & v) : + value(boost::lexical_cast(v)) + { + } + + template + ParameterValue(const IceUtil::Optional & v) : + value(v ? boost::lexical_cast(*v) : IceUtil::Optional()) + { + } + + IceUtil::Optional value; + }; + typedef std::map Parameters; + + protected: + HttpClient(const std::string & baseUrl, const std::string & apikey); + + template + std::string + GetUrl(const std::string & pathFormat, const Params & ... params, const Parameters & parameters) const + { + boost::format path(pathFormat); + packParams(path, params...); + std::string pathStr(path.str()); + appendQueryParameters(pathStr, parameters); + return BaseURL + pathStr; + } + + template + IceInternal::Handle + GetData(const std::string & pathFormat, const Params & ... params, const Parameters & parameters) const + { + json::Value data = FetchJson(GetUrl(pathFormat, params..., parameters)); + return Slicer::Deserialize(data); + } + + static void packParams(boost::format &); + + template + static void packParams(boost::format & fmt, const T & param, const Ts & ... params) + { + fmt % param; + packParams(fmt, params...); + } + + void appendQueryParameters(std::string & path, const Parameters & parameters) const; + void appendQueryParameters(std::string & path, const Parameters::value_type & nvp) const; + json::Value FetchJson(const std::string & path) const; + + const std::string BaseURL; + const std::string ApiKey; + }; +}; + diff --git a/libtmdb/samples/movie_550.json b/libtmdb/samples/movie_550.json new file mode 100644 index 0000000..fe0b4a8 --- /dev/null +++ b/libtmdb/samples/movie_550.json @@ -0,0 +1,57 @@ +{ + "adult": false, + "backdrop_path": "/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg", + "belongs_to_collection": null, + "budget": 63000000, + "genres": [ + { + "id": 28, + "name": "Action" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 53, + "name": "Thriller" + } + ], + "homepage": "http://www.fightclub.com/", + "id": 550, + "imdb_id": "tt0137523", + "original_title": "Fight Club", + "overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.", + "popularity": 61151.745000000003, + "poster_path": "/2lECpi35Hnbpa4y46JX0aY3AWTy.jpg", + "production_companies": [ + { + "name": "20th Century Fox", + "id": 25 + } + ], + "production_countries": [ + { + "iso_3166_1": "DE", + "name": "Germany" + }, + { + "iso_3166_1": "US", + "name": "United States of America" + } + ], + "release_date": "1999-10-15", + "revenue": 100853753, + "runtime": 139, + "spoken_languages": [ + { + "iso_639_1": "en", + "name": "English" + } + ], + "status": "Released", + "tagline": "How much can you know about yourself if you've never been in a fight?", + "title": "Fight Club", + "vote_average": 9.0999999999999996, + "vote_count": 174 +} diff --git a/libtmdb/samples/searchMulti_breakingBad.json b/libtmdb/samples/searchMulti_breakingBad.json new file mode 100644 index 0000000..fb0ec82 --- /dev/null +++ b/libtmdb/samples/searchMulti_breakingBad.json @@ -0,0 +1,39 @@ +{ + "page": 1, + "results": [ + { + "backdrop_path": "/sIJyCJedGlZf1TId41gCtkblBGo.jpg", + "id": 1396, + "original_name": "Breaking Bad", + "first_air_date": "2008-01-19", + "poster_path": "/4yMXf3DW6oCL0lVPZaZM2GypgwE.jpg", + "popularity": 12.2665228348243, + "name": "Breaking Bad", + "vote_average": 9, + "vote_count": 68, + "media_type": "tv" + }, + { + "adult": false, + "backdrop_path": "/mMKahLSpwb9Yj2B0tB6vku3tkGy.jpg", + "id": 239459, + "original_title": "No Half Measures: Creating the Final Season of Breaking Bad", + "release_date": "2013-11-26", + "poster_path": "/8OixSR45U5dbqv8F0tlspmTbXxN.jpg", + "popularity": 2.8761099924108, + "title": "No Half Measures: Creating the Final Season of Breaking Bad", + "vote_average": 8.5, + "vote_count": 5, + "media_type": "movie" + }, + { + "adult": false, + "id": 287, + "name": "Brad Pitt", + "profile_path": "/w8zJQuN7tzlm6FY9mfGKihxp3Cb.jpg", + "media_type": "person" + } + ], + "total_pages": 1, + "total_results": 3 +} diff --git a/libtmdb/samples/tv_1396.json b/libtmdb/samples/tv_1396.json new file mode 100644 index 0000000..6043750 --- /dev/null +++ b/libtmdb/samples/tv_1396.json @@ -0,0 +1,89 @@ +{ + "backdrop_path": "/sIJyCJedGlZf1TId41gCtkblBGo.jpg", + "created_by": [ + { + "id": 66633, + "name": "Vince Gilligan", + "profile_path": "/rLSUjr725ez1cK7SKVxC9udO03Y.jpg" + } + ], + "episode_run_time": [ + 45, + 47 + ], + "first_air_date": "2008-01-19", + "genres": [ + { + "id": 18, + "name": "Drama" + } + ], + "homepage": "http://www.amctv.com/shows/breaking-bad", + "id": 1396, + "in_production": false, + "languages": [ + "en", + "de", + "ro", + "es", + "fa" + ], + "last_air_date": "2013-09-29", + "name": "Breaking Bad", + "networks": [ + { + "id": 174, + "name": "AMC" + } + ], + "number_of_episodes": 62, + "number_of_seasons": 5, + "original_name": "Breaking Bad", + "origin_country": [ + "US" + ], + "overview": "Breaking Bad is an American crime drama television series created and produced by Vince Gilligan. Set and produced in Albuquerque, New Mexico, Breaking Bad is the story of Walter White, a struggling high school chemistry teacher who is diagnosed with inoperable lung cancer at the beginning of the series. He turns to a life of crime, producing and selling methamphetamine, in order to secure his family's financial future before he dies, teaming with his former student, Jesse Pinkman. Heavily serialized, the series is known for positioning its characters in seemingly inextricable corners and has been labeled a contemporary western by its creator.", + "popularity": 7.68402647476576, + "poster_path": "/4yMXf3DW6oCL0lVPZaZM2GypgwE.jpg", + "seasons": [ + { + "air_date": "2009-02-17", + "id": 3577, + "poster_path": "/spPmYZAq2xLKQOEIdBPkhiRxrb9.jpg", + "season_number": 0 + }, + { + "air_date": "2008-01-19", + "id": 3572, + "poster_path": "/dHCYpEoHEjAV6Xt3eyNthkdLRl3.jpg", + "season_number": 1 + }, + { + "air_date": "2009-03-08", + "id": 3573, + "poster_path": "/ww6cDy0dhrVEdMqielNEsYz96mg.jpg", + "season_number": 2 + }, + { + "air_date": "2010-03-21", + "id": 3575, + "poster_path": "/rINvcsYHUprsx9L8zNr5JltALda.jpg", + "season_number": 3 + }, + { + "air_date": "2011-07-17", + "id": 3576, + "poster_path": "/ngnE7FFQqrrLgK3yVsv3kjwtQMZ.jpg", + "season_number": 4 + }, + { + "air_date": "2012-07-15", + "id": 3578, + "poster_path": "/ih1JKNxEzW56azeFpEQmdu4poA4.jpg", + "season_number": 5 + } + ], + "status": "Ended", + "vote_average": 9, + "vote_count": 72 +} diff --git a/libtmdb/testCallMockApi.cpp b/libtmdb/testCallMockApi.cpp new file mode 100644 index 0000000..08719ca --- /dev/null +++ b/libtmdb/testCallMockApi.cpp @@ -0,0 +1,26 @@ +#define BOOST_TEST_MODULE CallMockApi +#include + +#include "tmdb-proxy.h" + +const std::string MockBase = "http://private-5513-themoviedb.apiary-mock.com/3"; +const std::string ApiKey = "48b32823d2b60c5c1085af36daed03fa"; + +BOOST_AUTO_TEST_CASE( search_tmdb ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto resp = test.SearchMulti("breaking bad", 0, Ice::Current()); + BOOST_REQUIRE_EQUAL(3, resp->Results.size()); + BOOST_REQUIRE_EQUAL(3, resp->TotalResults); + BOOST_REQUIRE_EQUAL(1396, resp->Results[0]->Id); + BOOST_REQUIRE_EQUAL(239459, resp->Results[1]->Id); + BOOST_REQUIRE_EQUAL(19050, resp->Results[2]->Id); +} + +BOOST_AUTO_TEST_CASE( get_movie ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto resp = test.GetMovie(1396, Ice::Current()); + BOOST_REQUIRE_EQUAL("Fight Club", resp->Title); +} + diff --git a/libtmdb/testFormatUrls.cpp b/libtmdb/testFormatUrls.cpp new file mode 100644 index 0000000..f3aa2a8 --- /dev/null +++ b/libtmdb/testFormatUrls.cpp @@ -0,0 +1,58 @@ +#define BOOST_TEST_MODULE FormatUrls +#include + +#define private public +#define protected public + +#include "tmdb-proxy.h" + +const std::string MockBase = "http://private-5513-themoviedb.apiary-mock.com/3"; +const std::string ApiKey = "48b32823d2b60c5c1085af36daed03fa"; + +BOOST_AUTO_TEST_CASE( initialize_simple_proxy ) +{ + TMDb::Proxy test(MockBase, ApiKey); +} + +BOOST_AUTO_TEST_CASE( format_url_simple ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something/%d", 23, { }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something/23?apikey=48b32823d2b60c5c1085af36daed03fa", url); +} + +BOOST_AUTO_TEST_CASE( format_url_query ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something/%d", 23, { { "page", 5 } }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something/23?apikey=48b32823d2b60c5c1085af36daed03fa&page=5", url); +} + +BOOST_AUTO_TEST_CASE( format_url_multiple ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something", { { "page", 5}, {"query", "string" } }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something?apikey=48b32823d2b60c5c1085af36daed03fa&page=5&query=string", url); +} + +BOOST_AUTO_TEST_CASE( format_url_escaped ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something", { { "query", "sample string" } }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something?apikey=48b32823d2b60c5c1085af36daed03fa&query=sample%20string", url); +} + +BOOST_AUTO_TEST_CASE( format_url_optionalvalue ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something", { { "query", IceUtil::Optional(10) } }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something?apikey=48b32823d2b60c5c1085af36daed03fa&query=10", url); +} + +BOOST_AUTO_TEST_CASE( format_url_optionalnovalue ) +{ + TMDb::Proxy test(MockBase, ApiKey); + auto url = test.GetUrl("/something", { { "query", IceUtil::Optional() } }); + BOOST_REQUIRE_EQUAL("http://private-5513-themoviedb.apiary-mock.com/3/something?apikey=48b32823d2b60c5c1085af36daed03fa", url); +} + diff --git a/libtmdb/testModels.cpp b/libtmdb/testModels.cpp new file mode 100644 index 0000000..19e1df7 --- /dev/null +++ b/libtmdb/testModels.cpp @@ -0,0 +1,117 @@ +#define BOOST_TEST_MODULE Deserialize +#include + +#include +#include +#include + +namespace std { + std::ostream & + operator<<(std::ostream & o, const TMDb::Date & d) + { + o << d.Year << '-' << d.Month << '-' << d.Day; + return o; + } +}; + +BOOST_AUTO_TEST_CASE( deserialize_searchmulti_json ) +{ + auto results = Slicer::Deserialize("samples/searchMulti_breakingBad.json"); + BOOST_REQUIRE_EQUAL(1, results->Page); + BOOST_REQUIRE_EQUAL(1, results->TotalPages); + BOOST_REQUIRE_EQUAL(3, results->TotalResults); + + BOOST_REQUIRE_EQUAL("::TMDb::SearchMatchTv", results->Results[0]->ice_id()); + auto tv = TMDb::SearchMatchTvPtr::dynamicCast(results->Results[0]); + BOOST_REQUIRE_EQUAL("Breaking Bad", tv->Name); + BOOST_REQUIRE_EQUAL("/4yMXf3DW6oCL0lVPZaZM2GypgwE.jpg", tv->PosterPath); + BOOST_REQUIRE_EQUAL(1396, tv->Id); + + BOOST_REQUIRE_EQUAL("::TMDb::SearchMatchMovie", results->Results[1]->ice_id()); + auto movie = TMDb::SearchMatchMoviePtr::dynamicCast(results->Results[1]); + BOOST_REQUIRE_EQUAL("No Half Measures: Creating the Final Season of Breaking Bad", movie->Title); + BOOST_REQUIRE_EQUAL("/8OixSR45U5dbqv8F0tlspmTbXxN.jpg", movie->PosterPath); + BOOST_REQUIRE_EQUAL(239459, movie->Id); + + BOOST_REQUIRE_EQUAL("::TMDb::SearchMatchPerson", results->Results[2]->ice_id()); + auto person = TMDb::SearchMatchPersonPtr::dynamicCast(results->Results[2]); + BOOST_REQUIRE_EQUAL("Brad Pitt", person->Name); + BOOST_REQUIRE_EQUAL(287, person->Id); +} + +BOOST_AUTO_TEST_CASE( deserialize_movie_json ) +{ + auto movie = Slicer::Deserialize("samples/movie_550.json"); + BOOST_REQUIRE_EQUAL(550, movie->Id); + BOOST_REQUIRE_EQUAL(false, movie->Adult); + BOOST_REQUIRE_EQUAL("/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg", movie->BackdropPath); + BOOST_REQUIRE_EQUAL(3, movie->Genres.size()); + BOOST_REQUIRE_EQUAL(28, movie->Genres.front().Id); + BOOST_REQUIRE_EQUAL("Action", movie->Genres.front().Name); + BOOST_REQUIRE_EQUAL(63000000, movie->Budget); + BOOST_REQUIRE_EQUAL("http://www.fightclub.com/", movie->HomePage); + BOOST_REQUIRE_EQUAL("tt0137523", movie->ImdbId); + BOOST_REQUIRE_EQUAL("Fight Club", movie->OriginalTitle); + BOOST_REQUIRE_CLOSE(61151.75, movie->Popularity, 0.1); + BOOST_REQUIRE_EQUAL("/2lECpi35Hnbpa4y46JX0aY3AWTy.jpg", movie->PosterPath); + BOOST_REQUIRE_EQUAL(295, movie->Overview.length()); + BOOST_REQUIRE_EQUAL(1, movie->ProductionCompanies.size()); + BOOST_REQUIRE_EQUAL(25, movie->ProductionCompanies.front().Id); + BOOST_REQUIRE_EQUAL("20th Century Fox", movie->ProductionCompanies.front().Name); + BOOST_REQUIRE_EQUAL(2, movie->ProductionCountries.size()); + BOOST_REQUIRE_EQUAL("DE", movie->ProductionCountries.front().Id); + BOOST_REQUIRE_EQUAL("Germany", movie->ProductionCountries.front().Name); + BOOST_REQUIRE_EQUAL(TMDb::Date({1999, 10, 15}), movie->ReleaseDate); + BOOST_REQUIRE_EQUAL(1, movie->SpokenLanguages.size()); + BOOST_REQUIRE_EQUAL("en", movie->SpokenLanguages.front().Id); + BOOST_REQUIRE_EQUAL("English", movie->SpokenLanguages.front().Name); + BOOST_REQUIRE_EQUAL(100853753, movie->Revenue); + BOOST_REQUIRE_EQUAL(139, movie->Runtime); + BOOST_REQUIRE_EQUAL("Released", movie->Status); + BOOST_REQUIRE_EQUAL("How much can you know about yourself if you've never been in a fight?", movie->Tagline); + BOOST_REQUIRE_EQUAL("Fight Club", movie->Title); + BOOST_REQUIRE_CLOSE(9.0999999, movie->VoteAverage, 0.01); + BOOST_REQUIRE_EQUAL(174, movie->VoteCount); +} + +BOOST_AUTO_TEST_CASE( deserialize_tvseries_json ) +{ + auto tvSeries = Slicer::Deserialize("samples/tv_1396.json"); + BOOST_REQUIRE_EQUAL("/sIJyCJedGlZf1TId41gCtkblBGo.jpg", tvSeries->BackdropPath); + BOOST_REQUIRE_EQUAL(1, tvSeries->CreatedBy.size()); + BOOST_REQUIRE_EQUAL(66633, tvSeries->CreatedBy.front().Id); + BOOST_REQUIRE_EQUAL("Vince Gilligan", tvSeries->CreatedBy.front().Name); + BOOST_REQUIRE_EQUAL("/rLSUjr725ez1cK7SKVxC9udO03Y.jpg", tvSeries->CreatedBy.front().ProfilePath); + BOOST_REQUIRE_EQUAL(2, tvSeries->EpisodeRunTimes.size()); + BOOST_REQUIRE_EQUAL(45, tvSeries->EpisodeRunTimes.front()); + BOOST_REQUIRE_EQUAL(TMDb::Date({2008, 1, 19}), tvSeries->FirstAirDate); + BOOST_REQUIRE_EQUAL(1, tvSeries->Genres.size()); + BOOST_REQUIRE_EQUAL(18, tvSeries->Genres.front().Id); + BOOST_REQUIRE_EQUAL("Drama", tvSeries->Genres.front().Name); + BOOST_REQUIRE_EQUAL("http://www.amctv.com/shows/breaking-bad", tvSeries->HomePage); + BOOST_REQUIRE_EQUAL(1396, tvSeries->Id); + BOOST_REQUIRE_EQUAL(false, tvSeries->InProduction); + BOOST_REQUIRE_EQUAL(5, tvSeries->Languages.size()); + BOOST_REQUIRE_EQUAL("en", tvSeries->Languages.front()); + BOOST_REQUIRE_EQUAL(TMDb::Date({2013, 9, 29}), tvSeries->LastAirDate); + BOOST_REQUIRE_EQUAL("Breaking Bad", tvSeries->Name); + BOOST_REQUIRE_EQUAL(1, tvSeries->Networks.size()); + BOOST_REQUIRE_EQUAL(174, tvSeries->Networks.front().Id); + BOOST_REQUIRE_EQUAL("AMC", tvSeries->Networks.front().Name); + BOOST_REQUIRE_EQUAL(62, tvSeries->NumberOfEpisodes); + BOOST_REQUIRE_EQUAL(5, tvSeries->NumberOfSeasons); + BOOST_REQUIRE_EQUAL(1, tvSeries->OriginCountries.size()); + BOOST_REQUIRE_EQUAL("US", tvSeries->OriginCountries.front()); + BOOST_REQUIRE_EQUAL(651, tvSeries->Overview.length()); + BOOST_REQUIRE_CLOSE(7.684, tvSeries->Popularity, 0.01); + BOOST_REQUIRE_EQUAL("/4yMXf3DW6oCL0lVPZaZM2GypgwE.jpg", tvSeries->PosterPath); + BOOST_REQUIRE_EQUAL(6, tvSeries->Seasons.size()); + BOOST_REQUIRE_EQUAL(TMDb::Date({2012, 7, 15}), tvSeries->Seasons.back().AirDate); + BOOST_REQUIRE_EQUAL(3578, tvSeries->Seasons.back().Id); + BOOST_REQUIRE_EQUAL("/ih1JKNxEzW56azeFpEQmdu4poA4.jpg", tvSeries->Seasons.back().PosterPath); + BOOST_REQUIRE_EQUAL(5, tvSeries->Seasons.back().SeasonNumber); + BOOST_REQUIRE_EQUAL("Ended", tvSeries->Status); + BOOST_REQUIRE_CLOSE(9, tvSeries->VoteAverage, 0.01); + BOOST_REQUIRE_EQUAL(72, tvSeries->VoteCount); +} + diff --git a/libtmdb/tmdb-api.ice b/libtmdb/tmdb-api.ice new file mode 100644 index 0000000..e116f2d --- /dev/null +++ b/libtmdb/tmdb-api.ice @@ -0,0 +1,21 @@ +#ifndef TMDB_API_ICE +#define TMDB_API_ICE + +#include "tmdb-models.ice" + +module TMDb { + exception HttpException { + int code; + }; + interface API { + idempotent SearchMultiResults SearchMulti(string query, optional(0) int page); + idempotent SearchMovieResults SearchMovies(string query, optional(1) int year, optional(0) int page); + idempotent SearchPersonResults SearchPersons(string query, optional(0) int page); + idempotent SearchTvResults SearchTv(string query, optional(0) int page); + idempotent Movie GetMovie(int id); + idempotent TvSeries GetTvSeries(int id); + }; +}; + +#endif + diff --git a/libtmdb/tmdb-common.ice b/libtmdb/tmdb-common.ice new file mode 100644 index 0000000..532d65b --- /dev/null +++ b/libtmdb/tmdb-common.ice @@ -0,0 +1,90 @@ +#ifndef TMDB_COMMON_ICE +#define TMDB_COMMON_ICE + +module TMDb { + struct Date { + short Year; + short Month; + short Day; + }; + + struct CountryRef { + ["slicer:name:iso_3166_1"] + string Id; + + ["slicer:name:name"] + string Name; + }; + sequence CountryRefList; + + struct CompanyRef { + ["slicer:name:id"] + int Id; + + ["slicer:name:name"] + string Name; + }; + sequence CompanyRefList; + + struct Language { + ["slicer:name:iso_639_1"] + string Id; + + ["slicer:name:name"] + string Name; + }; + sequence LanguageList; + + struct Genre { + ["slicer:name:id"] + int Id; + + ["slicer:name:name"] + string Name; + }; + sequence GenreList; + + struct PersonRef { + ["slicer:name:id"] + int Id; + + ["slicer:name:name"] + string Name; + + ["slicer:name:profile_path"] + string ProfilePath; + }; + sequence PersonRefList; + + struct SeasonRef { + ["slicer:name:air_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date AirDate; + + ["slicer:name:id"] + int Id; + + ["slicer:name:poster_path"] + string PosterPath; + + ["slicer:name:season_number"] + short SeasonNumber; + + }; + sequence SeasonRefList; + + struct NetworkRef { + ["slicer:name:id"] + int Id; + + ["slicer:name:name"] + string Name; + }; + sequence NetworkRefList; + + sequence Runtimes; + sequence StringList; +}; + +#endif + diff --git a/libtmdb/tmdb-models.ice b/libtmdb/tmdb-models.ice new file mode 100644 index 0000000..3368b8d --- /dev/null +++ b/libtmdb/tmdb-models.ice @@ -0,0 +1,269 @@ +#ifndef TMDB_MODELS_ICE +#define TMDB_MODELS_ICE + +#include "tmdb-common.ice" + +module TMDb { + class SearchPaging { + ["slicer:name:page"] + int Page; + + ["slicer:name:total_pages"] + int TotalPages; + + ["slicer:name:total_results"] + int TotalResults; + }; + + ["slicer:typeid:media_type"] + class SearchMatch { + ["slicer:name:id"] + int Id; + }; + sequence SearchMatchList; + + ["slicer:typename:person"] + class SearchMatchPerson extends SearchMatch { + ["slicer:name:adult"] + bool Adult; + + ["slicer:name:name"] + string Name; + + ["slicer:name:profile_path"] + string ProfilePath; + }; + sequence SearchMatchPersonList; + + ["slicer:typename:movie"] + class SearchMatchMovie extends SearchMatch { + ["slicer:name:adult"] + bool Adult; + + ["slicer:name:backdrop_path"] + string BackdropPath; + + ["slicer:name:original_title"] + string OriginalTitle; + + ["slicer:name:release_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date ReleaseDate; + + ["slicer:name:poster_path"] + string PosterPath; + + ["slicer:name:popularity"] + double Popularity; + + ["slicer:name:title"] + string Title; + + ["slicer:name:vote_average"] + float VoteAverage; + + ["slicer:name:vote_count"] + int VoteCount; + }; + sequence SearchMatchMovieList; + + ["slicer:typename:tv"] + class SearchMatchTv extends SearchMatch { + ["slicer:name:backdrop_path"] + string BackdropPath; + + ["slicer:name:original_name"] + string OriginalName; + + ["slicer:name:first_air_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date FirstAirDate; + + ["slicer:name:poster_path"] + string PosterPath; + + ["slicer:name:popularity"] + double Popularity; + + ["slicer:name:name"] + string Name; + + ["slicer:name:vote_average"] + float VoteAverage; + + ["slicer:name:vote_count"] + int VoteCount; + + ["slicer:name:media_type"] + string MediaType; + }; + sequence SearchMatchTvList; + + class SearchMultiResults extends SearchPaging { + ["slicer:name:results"] + SearchMatchList Results; + }; + + class SearchTvResults extends SearchPaging { + ["slicer:name:results"] + SearchMatchTvList Results; + }; + + class SearchMovieResults extends SearchPaging { + ["slicer:name:results"] + SearchMatchMovieList Results; + }; + + class SearchPersonResults extends SearchPaging { + ["slicer:name:results"] + SearchMatchPersonList Results; + }; + + class Movie { + ["slicer:name:adult"] + bool Adult; + + ["slicer:name:backdrop_path"] + string BackdropPath; + + ["slicer:name:belongs_to_collection"] + optional(0) int BelongsToCollection; + + ["slicer:name:budget"] + long Budget; + + ["slicer:name:genres"] + GenreList Genres; + + ["slicer:name:homepage"] + string HomePage; + + ["slicer:name:id"] + int Id; + + ["slicer:name:imdb_id"] + string ImdbId; + + ["slicer:name:original_title"] + string OriginalTitle; + + ["slicer:name:overview"] + string Overview; + + ["slicer:name:popularity"] + float Popularity; + + ["slicer:name:poster_path"] + string PosterPath; + + ["slicer:name:production_companies"] + CompanyRefList ProductionCompanies; + + ["slicer:name:production_countries"] + CountryRefList ProductionCountries; + + ["slicer:name:release_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date ReleaseDate; + + ["slicer:name:revenue"] + long Revenue; + + ["slicer:name:runtime"] + int Runtime; + + ["slicer:name:spoken_languages"] + LanguageList SpokenLanguages; + + ["slicer:name:status"] + string Status; + + ["slicer:name:tagline"] + string Tagline; + + ["slicer:name:title"] + string Title; + + ["slicer:name:vote_average"] + float VoteAverage; + + ["slicer:name:vote_count"] + int VoteCount; + }; + + class TvSeries { + ["slicer:name:backdrop_path"] + string BackdropPath; + + ["slicer:name:created_by"] + PersonRefList CreatedBy; + + ["slicer:name:episode_run_time"] + Runtimes EpisodeRunTimes; + + ["slicer:name:first_air_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date FirstAirDate; + + ["slicer:name:genres"] + GenreList Genres; + + ["slicer:name:homepage"] + string HomePage; + + ["slicer:name:id"] + int Id; + + ["slicer:name:in_production"] + bool InProduction; + + ["slicer:name:languages"] + StringList Languages; + + ["slicer:name:last_air_date", + "slicer:conversion:std.string:stringToDate:dateToString"] + Date LastAirDate; + + ["slicer:name:name"] + string Name; + + ["slicer:name:networks"] + NetworkRefList Networks; + + ["slicer:name:number_of_episodes"] + int NumberOfEpisodes; + + ["slicer:name:number_of_seasons"] + int NumberOfSeasons; + + ["slicer:name:original_name"] + string OriginalName; + + ["slicer:name:origin_country"] + StringList OriginCountries; + + ["slicer:name:overview"] + string Overview; + + ["slicer:name:popularity"] + float Popularity; + + ["slicer:name:poster_path"] + string PosterPath; + + ["slicer:name:seasons"] + SeasonRefList Seasons; + + ["slicer:name:status"] + string Status; + + ["slicer:name:vote_average"] + float VoteAverage; + + ["slicer:name:vote_count"] + int VoteCount; + }; +}; + +#endif + diff --git a/libtmdb/tmdb-proxy.cpp b/libtmdb/tmdb-proxy.cpp new file mode 100644 index 0000000..7a575e5 --- /dev/null +++ b/libtmdb/tmdb-proxy.cpp @@ -0,0 +1,46 @@ +#include "tmdb-proxy.h" + +namespace TMDb { + Proxy::Proxy(const std::string & bu, const std::string & k) : + HttpClient(bu, k) + { + } + + SearchMultiResultsPtr + Proxy::SearchMulti(const std::string & query, const IceUtil::Optional & page, const Ice::Current&) + { + return GetData("/search/multi", { { "query", query }, { "page", page } }); + } + + SearchMovieResultsPtr + Proxy::SearchMovies(const std::string & query, const IceUtil::Optional & year, const IceUtil::Optional & page, const Ice::Current&) + { + return GetData("/search/movies", { { "query", query }, { "page", page }, { "year", year } }); + } + + SearchPersonResultsPtr + Proxy::SearchPersons(const std::string & query, const IceUtil::Optional & page, const Ice::Current&) + { + return GetData("/search/person", { { "query", query }, { "page", page } }); + } + + SearchTvResultsPtr + Proxy::SearchTv(const std::string & query, const IceUtil::Optional & page, const Ice::Current&) + { + return GetData("/search/tv", { { "query", query}, { "page", page } }); + } + + MoviePtr + Proxy::GetMovie(Ice::Int id, const Ice::Current&) + { + return GetData("/movie/%d", id, { }); + } + + TvSeriesPtr + Proxy::GetTvSeries(Ice::Int id, const Ice::Current&) + { + return GetData("/tv/%d", id, { }); + } + +} + diff --git a/libtmdb/tmdb-proxy.h b/libtmdb/tmdb-proxy.h new file mode 100644 index 0000000..6ff8b38 --- /dev/null +++ b/libtmdb/tmdb-proxy.h @@ -0,0 +1,17 @@ +#include +#include "httpClient.h" + +namespace TMDb { + class Proxy : public API, private HttpClient { + public: + Proxy(const std::string & baseUrl, const std::string & apikey); + + SearchMultiResultsPtr SearchMulti(const std::string&, const IceUtil::Optional&, const Ice::Current&) override; + SearchMovieResultsPtr SearchMovies(const std::string&, const IceUtil::Optional&, const IceUtil::Optional&, const Ice::Current&) override; + SearchPersonResultsPtr SearchPersons(const std::string&, const IceUtil::Optional&, const Ice::Current&) override; + SearchTvResultsPtr SearchTv(const std::string&, const IceUtil::Optional&, const Ice::Current&) override; + MoviePtr GetMovie(Ice::Int, const Ice::Current&) override; + TvSeriesPtr GetTvSeries(Ice::Int, const Ice::Current&) override; + }; +}; + diff --git a/p2pvr/Jamfile.jam b/p2pvr/Jamfile.jam index bee95cd..4644151 100644 --- a/p2pvr/Jamfile.jam +++ b/p2pvr/Jamfile.jam @@ -36,7 +36,7 @@ alias p2daemonlib : glibmm : : : build-project daemon ; build-project carddaemon ; -install debuginstall : p2comp//p2pvrp2comp dvb//p2pvrdvb devices//p2pvrdevices lib//p2pvrlib carddaemon//p2pvrcarddaemon daemonbase//p2pvrdaemonbase daemon//p2pvrdaemon ice//p2pvrice : ./testing ; +install debuginstall : p2comp//p2pvrp2comp dvb//p2pvrdvb devices//p2pvrdevices lib//p2pvrlib carddaemon//p2pvrcarddaemon daemonbase//p2pvrdaemonbase daemon//p2pvrdaemon ice//p2pvrice ../libtmdb//tmdb : ./testing ; package.install install : : : p2comp carddaemon daemon ; import type ; diff --git a/p2pvr/daemon/Jamfile.jam b/p2pvr/daemon/Jamfile.jam index 338f65e..96f60cb 100644 --- a/p2pvr/daemon/Jamfile.jam +++ b/p2pvr/daemon/Jamfile.jam @@ -14,7 +14,9 @@ lib p2pvrdaemon : ../ice//p2pvrice ../lib//p2pvrlib ../dvb//p2pvrdvb + ../../libtmdb//tmdb ..//p2sql ../devices//p2pvrdevices ../daemonbase//p2pvrdaemonbase + ../../libtmdb//tmdb ; diff --git a/p2pvr/daemon/daemon.cpp b/p2pvr/daemon/daemon.cpp index a2739eb..374a8b4 100644 --- a/p2pvr/daemon/daemon.cpp +++ b/p2pvr/daemon/daemon.cpp @@ -1,4 +1,5 @@ #include +#include #include "localDevices.h" #include "globalDevices.h" #include "maintenance.h" @@ -8,6 +9,24 @@ #include "recorder.h" #include "recordings.h" #include +#include + +// These configure external components and so they live here +class P2PVRTMDb { + public: + static std::string apikey; + static std::string baseUrl; + INITOPTIONS; +}; +std::string P2PVRTMDb::apikey; +std::string P2PVRTMDb::baseUrl; + +DECLARE_OPTIONS(P2PVRTMDb, "P2PVR TMDb configuration") +("p2pvr.tmdb.apikey", Options::value(&P2PVRTMDb::apikey, "48b32823d2b60c5c1085af36daed03fa"), + "The Movie Database API key") +("p2pvr.tmdb.baseurl", Options::value(&P2PVRTMDb::baseUrl, "http://private-5513-themoviedb.apiary-mock.com/3"), + "The Movie Database API base URL") +END_OPTIONS(P2PVRTMDb); class P2PvrDaemon : public DaemonBase { public: @@ -26,6 +45,7 @@ class P2PvrDaemon : public DaemonBase { adapter->add(new Storage(), ic->stringToIdentity("Storage")); adapter->add(new Recorder(adapter, timer), ic->stringToIdentity("Recorder")); adapter->add(new Recordings(), ic->stringToIdentity("Recordings")); + adapter->add(new TMDb::Proxy(P2PVRTMDb::baseUrl, P2PVRTMDb::apikey), ic->stringToIdentity("TMDb")); } }; -- cgit v1.2.3