From ac35fbaf100c629bacc694cf41e9da146ce05681 Mon Sep 17 00:00:00 2001 From: Arsentiy Milchakov Date: Thu, 16 Feb 2017 13:11:41 +0300 Subject: [PATCH] [core] Booking extended info --- 3party/jansson/myjansson.cpp | 27 +- 3party/jansson/myjansson.hpp | 1 + android/jni/com/mapswithme/maps/Framework.cpp | 12 +- android/jni/com/mapswithme/maps/Framework.hpp | 12 +- android/jni/com/mapswithme/maps/Sponsored.cpp | 118 ++-- .../com/mapswithme/maps/review/Review.java | 73 +-- .../mapswithme/maps/review/ReviewAdapter.java | 18 +- .../maps/widget/placepage/ReviewAdapter.java | 8 +- .../Deprecated/MWMPlacePageEntity.mm | 2 +- iphone/Maps/UI/PlacePage/MWMPlacePageData.mm | 3 +- map/framework.cpp | 4 +- map/framework.hpp | 6 +- partners_api/booking_api.cpp | 519 ++++++++++-------- partners_api/booking_api.hpp | 177 ++---- .../partners_api_tests/booking_tests.cpp | 51 +- platform/http_client.cpp | 2 +- xcode/search/search.xcodeproj/project.pbxproj | 14 +- 17 files changed, 540 insertions(+), 507 deletions(-) diff --git a/3party/jansson/myjansson.cpp b/3party/jansson/myjansson.cpp index 5481a93ceb..9bd470b82a 100644 --- a/3party/jansson/myjansson.cpp +++ b/3party/jansson/myjansson.cpp @@ -12,7 +12,7 @@ void FromJSON(json_t * root, string & result) void FromJSONObject(json_t * root, string const & field, string & result) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) MYTHROW(my::Json::Exception, ("Obligatory field", field, "is absent.")); @@ -31,7 +31,7 @@ void FromJSONObject(json_t * root, string const & field, strings::UniString & re void FromJSONObject(json_t * root, string const & field, double & result) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) MYTHROW(my::Json::Exception, ("Obligatory field", field, "is absent.")); @@ -43,7 +43,7 @@ void FromJSONObject(json_t * root, string const & field, double & result) void FromJSONObject(json_t * root, string const & field, json_int_t & result) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) MYTHROW(my::Json::Exception, ("Obligatory field", field, "is absent.")); @@ -55,7 +55,7 @@ void FromJSONObject(json_t * root, string const & field, json_int_t & result) void FromJSONObjectOptionalField(json_t * root, string const & field, string & result) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) { @@ -70,7 +70,7 @@ void FromJSONObjectOptionalField(json_t * root, string const & field, string & r void FromJSONObjectOptionalField(json_t * root, string const & field, json_int_t & result) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) { @@ -82,10 +82,25 @@ void FromJSONObjectOptionalField(json_t * root, string const & field, json_int_t result = json_integer_value(val); } +void FromJSONObjectOptionalField(json_t * root, string const & field, double & result) +{ + if (!json_is_object(root)) + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); + json_t * val = json_object_get(root, field.c_str()); + if (!val) + { + result = 0.0; + return; + } + if (!json_is_number(val)) + MYTHROW(my::Json::Exception, ("The field", field, "must contain a json number.")); + result = json_number_value(val); +} + void FromJSONObjectOptionalField(json_t * root, string const & field, bool & result, bool def) { if (!json_is_object(root)) - MYTHROW(my::Json::Exception, ("Bad json object when parsing", field)); + MYTHROW(my::Json::Exception, ("Bad json object while parsing", field)); json_t * val = json_object_get(root, field.c_str()); if (!val) { diff --git a/3party/jansson/myjansson.hpp b/3party/jansson/myjansson.hpp index a86687232a..b9dc3089cc 100644 --- a/3party/jansson/myjansson.hpp +++ b/3party/jansson/myjansson.hpp @@ -53,6 +53,7 @@ void FromJSONObject(json_t * root, string const & field, vector & result) void FromJSONObjectOptionalField(json_t * root, string const & field, string & result); void FromJSONObjectOptionalField(json_t * root, string const & field, json_int_t & result); +void FromJSONObjectOptionalField(json_t * root, string const & field, double & result); void FromJSONObjectOptionalField(json_t * root, string const & field, bool & result, bool def = false); void FromJSONObjectOptionalField(json_t * root, string const & field, json_t *& result); diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 193fabefd4..117b2eea71 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -487,18 +487,18 @@ place_page::Info & Framework::GetPlacePageInfo() { return m_info; } -void Framework::RequestBookingMinPrice( - JNIEnv * env, jobject policy, string const & hotelId, string const & currencyCode, - function const & callback) +void Framework::RequestBookingMinPrice(JNIEnv * env, jobject policy, + string const & hotelId, string const & currencyCode, + booking::GetMinPriceCallback const & callback) { auto const bookingApi = m_work.GetBookingApi(ToNativeNetworkPolicy(env, policy)); if (bookingApi) bookingApi->GetMinPrice(hotelId, currencyCode, callback); } -void Framework::RequestBookingInfo(JNIEnv * env, jobject policy, string const & hotelId, - string const & lang, - function const & callback) +void Framework::RequestBookingInfo(JNIEnv * env, jobject policy, + string const & hotelId, string const & lang, + booking::GetHotelInfoCallback const & callback) { auto const bookingApi = m_work.GetBookingApi(ToNativeNetworkPolicy(env, policy)); if (bookingApi) diff --git a/android/jni/com/mapswithme/maps/Framework.hpp b/android/jni/com/mapswithme/maps/Framework.hpp index 593c6f4206..14b6614f1f 100644 --- a/android/jni/com/mapswithme/maps/Framework.hpp +++ b/android/jni/com/mapswithme/maps/Framework.hpp @@ -161,12 +161,12 @@ namespace android void SetPlacePageInfo(place_page::Info const & info); place_page::Info & GetPlacePageInfo(); - void RequestBookingMinPrice(JNIEnv * env, jobject policy, string const & hotelId, - string const & currency, - function const & callback); - void RequestBookingInfo(JNIEnv * env, jobject policy, string const & hotelId, - string const & lang, - function const & callback); + void RequestBookingMinPrice(JNIEnv * env, jobject policy, + string const & hotelId, string const & currency, + booking::GetMinPriceCallback const & callback); + void RequestBookingInfo(JNIEnv * env, jobject policy, + string const & hotelId, string const & lang, + booking::GetHotelInfoCallback const & callback); bool HasSpaceForMigration(); storage::TCountryId PreMigrate(ms::LatLon const & position, storage::Storage::TChangeCountryFunction const & statusChangeListener, diff --git a/android/jni/com/mapswithme/maps/Sponsored.cpp b/android/jni/com/mapswithme/maps/Sponsored.cpp index 76578faea1..2bc0a01c4e 100644 --- a/android/jni/com/mapswithme/maps/Sponsored.cpp +++ b/android/jni/com/mapswithme/maps/Sponsored.cpp @@ -10,6 +10,8 @@ namespace { +using namespace booking; + jclass g_sponsoredClass; jclass g_facilityTypeClass; jclass g_nearbyObjectClass; @@ -24,6 +26,7 @@ jmethodID g_hotelInfoConstructor; jmethodID g_sponsoredClassConstructor; jmethodID g_priceCallback; jmethodID g_infoCallback; +string g_lastRequestedHotelId; void PrepareClassRefs(JNIEnv * env, jclass sponsoredClass) { @@ -47,8 +50,8 @@ void PrepareClassRefs(JNIEnv * env, jclass sponsoredClass) g_imageConstructor = jni::GetConstructorID(env, g_imageClass, "(Ljava/lang/String;Ljava/lang/String;)V"); g_reviewConstructor = jni::GetConstructorID(env, g_reviewClass, - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;FJ)V"); + "(JFLjava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;)V"); g_hotelInfoConstructor = jni::GetConstructorID( env, g_hotelInfoClass, "(Ljava/lang/String;[Lcom/mapswithme/maps/gallery/Image;[Lcom/mapswithme/maps/widget/" @@ -69,10 +72,41 @@ void PrepareClassRefs(JNIEnv * env, jclass sponsoredClass) "(Ljava/lang/String;Lcom/mapswithme/maps/widget/placepage/Sponsored$HotelInfo;)V"); } +jobjectArray ToPhotosArray(JNIEnv * env, vector const & photos) +{ + return jni::ToJavaArray(env, g_imageClass, photos, + [](JNIEnv * env, HotelPhotoUrls const & item) { + return env->NewObject(g_imageClass, g_imageConstructor, + jni::ToJavaString(env, item.m_original), + jni::ToJavaString(env, item.m_small)); + }); +} + +jobjectArray ToFacilitiesArray(JNIEnv * env, vector const & facilities) +{ + return jni::ToJavaArray(env, g_facilityTypeClass, facilities, + [](JNIEnv * env, HotelFacility const & item) { + return env->NewObject(g_facilityTypeClass, g_facilityConstructor, + jni::ToJavaString(env, item.m_facilityType), + jni::ToJavaString(env, item.m_name)); + }); +} + +jobjectArray ToReviewsArray(JNIEnv * env, vector const & reviews) +{ + return jni::ToJavaArray(env, g_reviewClass, reviews, + [](JNIEnv * env, HotelReview const & item) { + return env->NewObject( + g_reviewClass, g_reviewConstructor, + time_point_cast(item.m_date).time_since_epoch().count(), + item.m_score, jni::ToJavaString(env, item.m_author), + jni::ToJavaString(env, item.m_pros), jni::ToJavaString(env, item.m_cons)); + }); +} } // namespace -extern "C" { - +extern "C" +{ // static Sponsored nativeGetCurrent(); JNIEXPORT jobject JNICALL Java_com_mapswithme_maps_widget_placepage_Sponsored_nativeGetCurrent( JNIEnv * env, jclass clazz) @@ -98,17 +132,21 @@ JNIEXPORT void JNICALL Java_com_mapswithme_maps_widget_placepage_Sponsored_nativ PrepareClassRefs(env, clazz); string const hotelId = jni::ToNativeString(env, id); + g_lastRequestedHotelId = hotelId; + string const code = jni::ToNativeString(env, currencyCode); - g_framework->RequestBookingMinPrice( - env, policy, hotelId, code, [hotelId](string const & price, string const & currency) { - GetPlatform().RunOnGuiThread([=]() { - JNIEnv * env = jni::GetEnv(); - env->CallStaticVoidMethod(g_sponsoredClass, g_priceCallback, - jni::ToJavaString(env, hotelId), jni::ToJavaString(env, price), - jni::ToJavaString(env, currency)); - }); + g_framework->RequestBookingMinPrice(env, policy, hotelId, code, + [](string const & hotelId, string const & price, string const & currency) { + GetPlatform().RunOnGuiThread([hotelId, price, currency]() { + if (g_lastRequestedHotelId != hotelId) + return; + + JNIEnv * env = jni::GetEnv(); + env->CallStaticVoidMethod(g_sponsoredClass, g_priceCallback, jni::ToJavaString(env, hotelId), + jni::ToJavaString(env, price), jni::ToJavaString(env, currency)); }); + }); } // static void nativeRequestInfo(String id, String locale); @@ -118,48 +156,26 @@ JNIEXPORT void JNICALL Java_com_mapswithme_maps_widget_placepage_Sponsored_nativ PrepareClassRefs(env, clazz); string const hotelId = jni::ToNativeString(env, id); + g_lastRequestedHotelId = hotelId; + string const code = jni::ToNativeString(env, locale); - g_framework->RequestBookingInfo( - env, policy, hotelId, code, [hotelId](BookingApi::HotelInfo const & hotelInfo) { - GetPlatform().RunOnGuiThread([=]() { - JNIEnv * env = jni::GetEnv(); + g_framework->RequestBookingInfo(env, policy, hotelId, code, [hotelId](HotelInfo const & hotelInfo) { + GetPlatform().RunOnGuiThread([hotelId, hotelInfo]() { + if (g_lastRequestedHotelId != hotelId) + return; + JNIEnv * env = jni::GetEnv(); - auto description = jni::ToJavaString(env, hotelInfo.m_description); - auto photos = - jni::ToJavaArray(env, g_imageClass, hotelInfo.m_photos, - [](JNIEnv * env, BookingApi::HotelPhotoUrls const & item) { - return env->NewObject(g_imageClass, g_imageConstructor, - jni::ToJavaString(env, item.m_original), - jni::ToJavaString(env, item.m_small)); - }); - auto facilities = jni::ToJavaArray(env, g_facilityTypeClass, hotelInfo.m_facilities, - [](JNIEnv * env, BookingApi::Facility const & item) { - return env->NewObject( - g_facilityTypeClass, g_facilityConstructor, - jni::ToJavaString(env, item.m_id), - jni::ToJavaString(env, item.m_localizedName)); - }); - auto reviews = jni::ToJavaArray( - env, g_reviewClass, hotelInfo.m_reviews, - [](JNIEnv * env, BookingApi::HotelReview const & item) { - return env->NewObject( - g_reviewClass, g_reviewConstructor, - jni::ToJavaString(env, item.m_reviewNeutral), - jni::ToJavaString(env, item.m_reviewPositive), - jni::ToJavaString(env, item.m_reviewNegative), - jni::ToJavaString(env, item.m_author), - jni::ToJavaString(env, item.m_authorPictUrl), item.m_rating, - time_point_cast(item.m_date).time_since_epoch().count()); - }); - auto nearby = env->NewObjectArray(0, g_nearbyObjectClass, 0); + auto description = jni::ToJavaString(env, hotelInfo.m_description); + auto photos = ToPhotosArray(env, hotelInfo.m_photos); + auto facilities = ToFacilitiesArray(env, hotelInfo.m_facilities); + auto reviews = ToReviewsArray(env, hotelInfo.m_reviews); + auto nearby = env->NewObjectArray(0, g_nearbyObjectClass, 0); - env->CallStaticVoidMethod( - g_sponsoredClass, g_infoCallback, jni::ToJavaString(env, hotelId), - env->NewObject(g_hotelInfoClass, g_hotelInfoConstructor, description, photos, - facilities, reviews, nearby)); - }); - }); + env->CallStaticVoidMethod(g_sponsoredClass, g_infoCallback, jni::ToJavaString(env, hotelId), + env->NewObject(g_hotelInfoClass, g_hotelInfoConstructor, description, + photos, facilities, reviews, nearby)); + }); + }); } - } // extern "C" diff --git a/android/src/com/mapswithme/maps/review/Review.java b/android/src/com/mapswithme/maps/review/Review.java index bc55fc569e..9fef43ebe2 100644 --- a/android/src/com/mapswithme/maps/review/Review.java +++ b/android/src/com/mapswithme/maps/review/Review.java @@ -7,54 +7,42 @@ import android.support.annotation.Nullable; public class Review implements Parcelable { - @Nullable - private final String mReview; - @Nullable - private final String mReviewPositive; - @Nullable - private final String mReviewNegative; + private final long mDate; + private final float mRating; @NonNull private final String mAuthor; - @NonNull - private final String mAuthorAvatar; - private final float mRating; - private final long mDate; + @Nullable + private final String mPros; + @Nullable + private final String mCons; - public Review(@Nullable String review, @Nullable String reviewPositive, - @Nullable String reviewNegative, @NonNull String author, - @NonNull String authorAvatar, - float rating, long date) + public Review(long date, float rating, @NonNull String author, @Nullable String pros, + @Nullable String cons) { - mReview = review; - mReviewPositive = reviewPositive; - mReviewNegative = reviewNegative; - mAuthor = author; - mAuthorAvatar = authorAvatar; - mRating = rating; mDate = date; + mRating = rating; + mAuthor = author; + mPros = pros; + mCons = cons; } protected Review(Parcel in) { - mReview = in.readString(); - mReviewPositive = in.readString(); - mReviewNegative = in.readString(); - mAuthor = in.readString(); - mAuthorAvatar = in.readString(); - mRating = in.readFloat(); mDate = in.readLong(); + mRating = in.readFloat(); + mAuthor = in.readString(); + mPros = in.readString(); + mCons = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mReview); - dest.writeString(mReviewPositive); - dest.writeString(mReviewNegative); - dest.writeString(mAuthor); - dest.writeString(mAuthorAvatar); - dest.writeFloat(mRating); dest.writeLong(mDate); + dest.writeFloat(mRating); + dest.writeString(mAuthor); + dest.writeString(mPros); + dest.writeString(mCons); } @Override @@ -79,21 +67,15 @@ public class Review implements Parcelable }; @Nullable - public String getReview() + public String getPros() { - return mReview; + return mPros; } @Nullable - public String getReviewPositive() + public String getCons() { - return mReviewPositive; - } - - @Nullable - public String getReviewNegative() - { - return mReviewNegative; + return mCons; } @NonNull @@ -102,13 +84,6 @@ public class Review implements Parcelable return mAuthor; } - @SuppressWarnings("unused") - @NonNull - public String getAuthorAvatar() - { - return mAuthorAvatar; - } - public float getRating() { return mRating; diff --git a/android/src/com/mapswithme/maps/review/ReviewAdapter.java b/android/src/com/mapswithme/maps/review/ReviewAdapter.java index b4ea102f97..eb067dc21d 100644 --- a/android/src/com/mapswithme/maps/review/ReviewAdapter.java +++ b/android/src/com/mapswithme/maps/review/ReviewAdapter.java @@ -127,7 +127,6 @@ class ReviewAdapter extends RecyclerView.Adapter final TextView mUserName; final TextView mCommentDate; final TextView mRating; - final TextView mReview; final View mPositiveReview; final TextView mTvPositiveReview; final View mNegativeReview; @@ -140,7 +139,6 @@ class ReviewAdapter extends RecyclerView.Adapter mUserName = (TextView) itemView.findViewById(R.id.tv__user_name); mCommentDate = (TextView) itemView.findViewById(R.id.tv__comment_date); mRating = (TextView) itemView.findViewById(R.id.tv__user_rating); - mReview = (TextView) itemView.findViewById(R.id.tv__review); mPositiveReview = itemView.findViewById(R.id.ll__positive_review); mTvPositiveReview = (TextView) itemView.findViewById(R.id.tv__positive_review); mNegativeReview = itemView.findViewById(R.id.ll__negative_review); @@ -156,31 +154,23 @@ class ReviewAdapter extends RecyclerView.Adapter Date date = new Date(item.getDate()); mCommentDate.setText(DateFormat.getMediumDateFormat(mCommentDate.getContext()).format(date)); mRating.setText(String.format(Locale.getDefault(), "%.1f", item.getRating())); - if (TextUtils.isEmpty(item.getReviewPositive())) + if (TextUtils.isEmpty(item.getPros())) { UiUtils.hide(mPositiveReview); } else { UiUtils.show(mPositiveReview); - mTvPositiveReview.setText(item.getReviewPositive()); + mTvPositiveReview.setText(item.getPros()); } - if (TextUtils.isEmpty(item.getReviewNegative())) + if (TextUtils.isEmpty(item.getCons())) { UiUtils.hide(mNegativeReview); } else { UiUtils.show(mNegativeReview); - mTvNegativeReview.setText(item.getReviewNegative()); - } - if (UiUtils.isHidden(mNegativeReview) && UiUtils.isHidden(mPositiveReview)) - { - UiUtils.showIf(!TextUtils.isEmpty(item.getReview()), mReview); - } - else - { - UiUtils.hide(mReview); + mTvNegativeReview.setText(item.getCons()); } } } diff --git a/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java index 901acbe98a..70043648d7 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java +++ b/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java @@ -113,23 +113,23 @@ class ReviewAdapter extends BaseAdapter Date date = new Date(item.getDate()); mCommentDate.setText(DateFormat.getMediumDateFormat(mCommentDate.getContext()).format(date)); mRating.setText(String.format(Locale.getDefault(), "%.1f", item.getRating())); - if (TextUtils.isEmpty(item.getReviewPositive())) + if (TextUtils.isEmpty(item.getPros())) { UiUtils.hide(mPositiveReview); } else { UiUtils.show(mPositiveReview); - mTvPositiveReview.setText(item.getReviewPositive()); + mTvPositiveReview.setText(item.getPros()); } - if (TextUtils.isEmpty(item.getReviewNegative())) + if (TextUtils.isEmpty(item.getCons())) { UiUtils.hide(mNegativeReview); } else { UiUtils.show(mNegativeReview); - mTvNegativeReview.setText(item.getReviewNegative()); + mTvNegativeReview.setText(item.getCons()); } } } diff --git a/iphone/Maps/UI/PlacePage/Deprecated/MWMPlacePageEntity.mm b/iphone/Maps/UI/PlacePage/Deprecated/MWMPlacePageEntity.mm index c8bcbaf4d3..a2b137781e 100644 --- a/iphone/Maps/UI/PlacePage/Deprecated/MWMPlacePageEntity.mm +++ b/iphone/Maps/UI/PlacePage/Deprecated/MWMPlacePageEntity.mm @@ -141,7 +141,7 @@ void initFieldsMap() return; } auto success = [self, completion, failure, currency, currencyFormatter]( - string const & minPrice, string const & priceCurrency) { + string const & hotelId, string const & minPrice, string const & priceCurrency) { if (currency != priceCurrency) { failure(); diff --git a/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm b/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm index 5daec097cb..168637de69 100644 --- a/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm +++ b/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm @@ -248,7 +248,8 @@ using namespace place_page; string const currency = currencyFormatter.currencyCode.UTF8String; - auto const func = [self, label, currency, currencyFormatter](string const & minPrice, + auto const func = [self, label, currency, currencyFormatter](string const & hotelId, + string const & minPrice, string const & priceCurrency) { if (currency != priceCurrency) return; diff --git a/map/framework.cpp b/map/framework.cpp index 09663c52b2..b639fdd88a 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -471,7 +471,7 @@ Framework::~Framework() m_model.SetOnMapDeregisteredCallback(nullptr); } -BookingApi * Framework::GetBookingApi(platform::NetworkPolicy const & policy) +booking::Api * Framework::GetBookingApi(platform::NetworkPolicy const & policy) { if (policy.CanUse()) return m_bookingApi.get(); @@ -479,7 +479,7 @@ BookingApi * Framework::GetBookingApi(platform::NetworkPolicy const & policy) return nullptr; } -BookingApi const * Framework::GetBookingApi(platform::NetworkPolicy const & policy) const +booking::Api const * Framework::GetBookingApi(platform::NetworkPolicy const & policy) const { if (policy.CanUse()) return m_bookingApi.get(); diff --git a/map/framework.hpp b/map/framework.hpp index 41b8f04fc7..264c40b69f 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -165,7 +165,7 @@ protected: BookmarkManager m_bmManager; - unique_ptr m_bookingApi = make_unique(); + unique_ptr m_bookingApi = make_unique(); unique_ptr m_uberApi = make_unique(); df::DrapeApi m_drapeApi; @@ -199,8 +199,8 @@ public: virtual ~Framework(); /// Get access to booking api helpers - BookingApi * GetBookingApi(platform::NetworkPolicy const & policy); - BookingApi const * GetBookingApi(platform::NetworkPolicy const & policy) const; + booking::Api * GetBookingApi(platform::NetworkPolicy const & policy); + booking::Api const * GetBookingApi(platform::NetworkPolicy const & policy) const; uber::Api * GetUberApi(platform::NetworkPolicy const & policy); df::DrapeApi & GetDrapeApi() { return m_drapeApi; } diff --git a/partners_api/booking_api.cpp b/partners_api/booking_api.cpp index 23564f07f5..45cf5bec6e 100644 --- a/partners_api/booking_api.cpp +++ b/partners_api/booking_api.cpp @@ -1,37 +1,240 @@ #include "partners_api/booking_api.hpp" +#include "platform/http_client.hpp" +#include "platform/platform.hpp" + #include "base/gmtime.hpp" #include "base/logging.hpp" +#include "base/thread.hpp" -#include "std/chrono.hpp" +#include "std/initializer_list.hpp" +#include "std/iomanip.hpp" #include "std/iostream.hpp" #include "std/sstream.hpp" +#include "std/utility.hpp" #include "3party/jansson/myjansson.hpp" #include "private.h" -char constexpr BookingApi::kDefaultCurrency[1]; - -BookingApi::BookingApi() : m_affiliateId(BOOKING_AFFILIATE_ID), m_testingMode(false) +namespace { - stringstream ss; - ss << BOOKING_KEY << ":" << BOOKING_SECRET; - m_apiUrl = "https://" + ss.str() + "@distribution-xml.booking.com/json/bookings."; +using namespace platform; +using namespace booking; + +string const kBookingApiBaseUrl = "https://distribution-xml.booking.com/json/bookings"; +string const kExtendedHotelInfoBaseUrl = ""; +string const kPhotoOriginalUrl = "http://aff.bstatic.com/images/hotel/max500/"; +string const kPhotoSmallUrl = "http://aff.bstatic.com/images/hotel/square60/"; + +bool RunSimpleHttpRequest(string const & url, string & result) +{ + HttpClient request(url); + request.SetUserAndPassword(BOOKING_KEY, BOOKING_SECRET); + if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200) + { + result = request.ServerResponse(); + return true; + } + return false; } -string BookingApi::GetBookHotelUrl(string const & baseUrl, string const & /* lang */) const +string MakeApiUrl(string const & func, initializer_list> const & params, + bool testing) { - return GetDescriptionUrl(baseUrl) + "#availability"; + ostringstream os; + os << kBookingApiBaseUrl << "." << func << "?"; + + bool firstRun = true; + for (auto const & param : params) + { + if (firstRun) + { + firstRun = false; + os << ""; + } + else + { + os << "&"; + } + os << param.first << "=" << param.second; + } + + if (testing) + os << "&show_test=1"; + + return os.str(); } -string BookingApi::GetDescriptionUrl(string const & baseUrl, string const & /* lang */) const +void ClearHotelInfo(HotelInfo & info) { - return baseUrl + "?aid=" + m_affiliateId; + info.m_hotelId.clear(); + info.m_description.clear(); + info.m_photos.clear(); + info.m_facilities.clear(); + info.m_reviews.clear(); + info.m_score = 0.0; + info.m_scoreCount = 0; } -void BookingApi::GetMinPrice(string const & hotelId, string const & currency, - function const & fn) +vector ParseFacilities(json_t const * facilitiesArray) +{ + vector facilities; + + if (facilitiesArray == nullptr || !json_is_array(facilitiesArray)) + return facilities; + + size_t sz = json_array_size(facilitiesArray); + + for (size_t i = 0; i < sz; ++i) + { + auto item = json_array_get(facilitiesArray, i); + + HotelFacility facility; + my::FromJSONObject(item, "facility_type", facility.m_facilityType); + my::FromJSONObject(item, "name", facility.m_name); + + facilities.push_back(move(facility)); + } + + return facilities; +} + +vector ParsePhotos(json_t const * photosArray) +{ + if (photosArray == nullptr || !json_is_array(photosArray)) + return {}; + + vector photos; + size_t sz = json_array_size(photosArray); + string photoId; + + for (size_t i = 0; i < sz; ++i) + { + auto item = json_array_get(photosArray, i); + my::FromJSON(item, photoId); + + // First three digits of id are used as part of path to photo on the server. + if (photoId.size() < 3) + { + LOG(LWARNING, ("Incorrect photo id =", photoId)); + continue; + } + + string url(photoId.substr(0, 3) + "/" + photoId + ".jpg"); + photos.push_back({kPhotoSmallUrl + url, kPhotoOriginalUrl + url}); + } + + return photos; +} + +vector ParseReviews(json_t const * reviewsArray) +{ + if (reviewsArray == nullptr || !json_is_array(reviewsArray)) + return {}; + + vector reviews; + size_t sz = json_array_size(reviewsArray); + string date; + + for (size_t i = 0; i < sz; ++i) + { + auto item = json_array_get(reviewsArray, i); + HotelReview review; + + my::FromJSONObject(item, "date", date); + istringstream ss(date); + tm t = {}; + ss >> get_time(&t, "%Y-%m-%d %H:%M:%S"); + if (ss.fail()) + { + LOG(LWARNING, ("Incorrect review date =", date)); + continue; + } + review.m_date = system_clock::from_time_t(mktime(&t)); + + double score; + my::FromJSONObject(item, "average_score", score); + review.m_score = static_cast(score); + + my::FromJSONObject(item, "author", review.m_author); + my::FromJSONObject(item, "pros", review.m_pros); + my::FromJSONObject(item, "cons", review.m_cons); + + reviews.push_back(move(review)); + } + + return reviews; +} + +void FillHotelInfo(string const & src, HotelInfo & info) +{ + my::Json root(src.c_str()); + + my::FromJSONObjectOptionalField(root.get(), "description", info.m_description); + double score; + my::FromJSONObjectOptionalField(root.get(), "average_score", score); + info.m_score = static_cast(score); + + json_int_t scoreCount = 0; + my::FromJSONObjectOptionalField(root.get(), "score_count", scoreCount); + info.m_scoreCount = scoreCount; + + auto const facilitiesArray = json_object_get(root.get(), "facilities"); + info.m_facilities = ParseFacilities(facilitiesArray); + + auto const photosArray = json_object_get(root.get(), "photos"); + info.m_photos = ParsePhotos(photosArray); + + auto const reviewsArray = json_object_get(root.get(), "reviews"); + info.m_reviews = ParseReviews(reviewsArray); +} + +void FillPriceAndCurrency(string const & src, string const & currency, string & minPrice, + string & priceCurrency) +{ + my::Json root(src.c_str()); + if (!json_is_array(root.get())) + MYTHROW(my::Json::Exception, ("The answer must contain a json array.")); + size_t const rootSize = json_array_size(root.get()); + + if (rootSize == 0) + return; + + // Read default hotel price and currency. + auto obj = json_array_get(root.get(), 0); + my::FromJSONObject(obj, "min_price", minPrice); + my::FromJSONObject(obj, "currency_code", priceCurrency); + + if (currency.empty() || priceCurrency == currency) + return; + + // Try to get price in requested currency. + json_t * arr = json_object_get(obj, "other_currency"); + if (arr == nullptr || !json_is_array(arr)) + return; + + size_t sz = json_array_size(arr); + string code; + for (size_t i = 0; i < sz; ++i) + { + auto el = json_array_get(arr, i); + my::FromJSONObject(el, "currency_code", code); + if (code == currency) + { + priceCurrency = code; + my::FromJSONObject(el, "min_price", minPrice); + break; + } + } +} +} // namespace + +namespace booking +{ +// static +bool RawApi::GetHotelAvailability(string const & hotelId, string const & currency, string & result, + bool testing /* = false */) { char dateArrival[12]{}; char dateDeparture[12]{}; @@ -45,48 +248,69 @@ void BookingApi::GetMinPrice(string const & hotelId, string const & currency, string url = MakeApiUrl("getHotelAvailability", {{"hotel_ids", hotelId}, {"currency_code", currency}, {"arrival_date", dateArrival}, - {"departure_date", dateDeparture}}); - auto const callback = [this, fn, currency](downloader::HttpRequest & answer) - { + {"departure_date", dateDeparture}}, + testing); + return RunSimpleHttpRequest(url, result); +} +// static +bool RawApi::GetExtendedInfo(string const & hotelId, string const & lang, string & result) +{ + ostringstream os; + os << kExtendedHotelInfoBaseUrl; + // Last three digits of id are used as part of path to hotel extended info on the server. + if (hotelId.size() > 3) + os << hotelId.substr(hotelId.size() - 3); + else + os << "000"; + string langCode; + if (lang.empty()) + { + langCode = "en"; + } + else + { + auto const pos = lang.find('_'); + if (pos != string::npos) + langCode = lang.substr(0, pos); + else + langCode = lang; + } + + os << "/" << hotelId << "/" << langCode << ".json"; + + return RunSimpleHttpRequest(os.str(), result); +} + +string Api::GetBookHotelUrl(string const & baseUrl) const +{ + return GetDescriptionUrl(baseUrl) + "#availability"; +} + +string Api::GetDescriptionUrl(string const & baseUrl) const +{ + return baseUrl + string("?aid=") + BOOKING_AFFILIATE_ID; +} + +void Api::GetMinPrice(string const & hotelId, string const & currency, + GetMinPriceCallback const & fn) +{ + auto const testingMode = m_testingMode; + + threads::SimpleThread([hotelId, currency, fn, testingMode]() + { string minPrice; string priceCurrency; + string httpResult; + if (!RawApi::GetHotelAvailability(hotelId, currency, httpResult, testingMode)) + { + fn(hotelId, minPrice, priceCurrency); + return; + } + try { - my::Json root(answer.Data().c_str()); - if (!json_is_array(root.get())) - MYTHROW(my::Json::Exception, ("The answer must contain a json array.")); - size_t const sz = json_array_size(root.get()); - - if (sz > 0) - { - // Read default hotel price and currency. - auto obj = json_array_get(root.get(), 0); - my::FromJSONObject(obj, "min_price", minPrice); - my::FromJSONObject(obj, "currency_code", priceCurrency); - - // Try to get price in requested currency. - if (!currency.empty() && priceCurrency != currency) - { - json_t * arr = json_object_get(obj, "other_currency"); - if (arr && json_is_array(arr)) - { - size_t sz = json_array_size(arr); - for (size_t i = 0; i < sz; ++i) - { - auto el = json_array_get(arr, i); - string code; - my::FromJSONObject(el, "currency_code", code); - if (code == currency) - { - priceCurrency = code; - my::FromJSONObject(el, "min_price", minPrice); - break; - } - } - } - } - } + FillPriceAndCurrency(httpResult, currency, minPrice, priceCurrency); } catch (my::Json::Exception const & e) { @@ -94,180 +318,35 @@ void BookingApi::GetMinPrice(string const & hotelId, string const & currency, minPrice.clear(); priceCurrency.clear(); } - fn(minPrice, priceCurrency); - m_request.reset(); - }; - - m_request.reset(downloader::HttpRequest::Get(url, callback)); + fn(hotelId, minPrice, priceCurrency); + }).detach(); } -// TODO(mgsergio): This is just a mockup, make it a real function. -void BookingApi::GetHotelInfo(string const & hotelId, string const & /* lang */, - function const & fn) +void Api::GetHotelInfo(string const & hotelId, string const & lang, GetHotelInfoCallback const & fn) { - HotelInfo info; + threads::SimpleThread([hotelId, lang, fn]() + { + HotelInfo info; + info.m_hotelId = hotelId; - info.m_hotelId = "000"; - info.m_description = "Interesting place among SoHo, Little " - "Italy and China town. Modern design. " - "Great view from roof. Near subway. " - "Free refreshment every afternoon. " - "The staff was very friendly."; + string result; + if (!RawApi::GetExtendedInfo(hotelId, lang, result)) + { + fn(info); + return; + } - info.m_photos.push_back({ - "http://storage9.static.itmages.ru/i/16/0915/h_1473944906_4427771_63a7c2282b.jpg", - "http://storage7.static.itmages.ru/i/16/0915/h_1473945189_5545647_db54564f06.jpg"}); + try + { + FillHotelInfo(result, info); + } + catch (my::Json::Exception const & e) + { + LOG(LERROR, (e.Msg())); + ClearHotelInfo(info); + } - info.m_photos.push_back({ - "http://storage9.static.itmages.ru/i/16/0915/h_1473944906_1573275_450fcd78b0.jpg", - "http://storage8.static.itmages.ru/i/16/0915/h_1473945194_6402871_b68c63c705.jpg"}); - - info.m_photos.push_back({ - "http://storage1.static.itmages.ru/i/16/0915/h_1473944906_6998375_f1ba6024a5.jpg", - "http://storage7.static.itmages.ru/i/16/0915/h_1473945188_9401486_7185c713bc.jpg"}); - - info.m_photos.push_back({ - "http://storage7.static.itmages.ru/i/16/0915/h_1473944904_8294064_035b4328ee.jpg", - "http://storage9.static.itmages.ru/i/16/0915/h_1473945189_8999398_d9bfe0d56d.jpg"}); - - info.m_photos.push_back({ - "http://storage6.static.itmages.ru/i/16/0915/h_1473944904_2231876_680171f67f.jpg", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945190_2042562_c6cfcccd18.jpg"}); - - info.m_photos.push_back({ - "http://storage7.static.itmages.ru/i/16/0915/h_1473944904_2871576_660e0aad58.jpg", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945190_9605355_94164142b7.jpg"}); - - info.m_photos.push_back({ - "http://storage8.static.itmages.ru/i/16/0915/h_1473944905_3578559_d4e95070e9.jpg", - "http://storage3.static.itmages.ru/i/16/0915/h_1473945190_3367031_145793d530.jpg"}); - - info.m_photos.push_back({ - "http://storage8.static.itmages.ru/i/16/0915/h_1473944905_5596402_9bdce96ace.jpg", - "http://storage4.static.itmages.ru/i/16/0915/h_1473945191_2783367_2440027ece.jpg"}); - - info.m_photos.push_back({ - "http://storage8.static.itmages.ru/i/16/0915/h_1473944905_4312757_433c687f4d.jpg", - "http://storage6.static.itmages.ru/i/16/0915/h_1473945191_1817571_b945aa1f3e.jpg"}); - - info.m_facilities = { - {"non_smoking_rooms", "Non smoking rooms"}, - {"gym", "Training gym"}, - {"pets_are_allowed", "Pets are allowed"} - }; - - info.m_reviews = { - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous1", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_5332083_b44af613bd.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_7504873_be0fe246e3.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945374_9397526_996bbca0d7.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous1", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_5332083_b44af613bd.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_7504873_be0fe246e3.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945374_9397526_996bbca0d7.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous1", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_5332083_b44af613bd.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_7504873_be0fe246e3.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945374_9397526_996bbca0d7.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous1", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_5332083_b44af613bd.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage2.static.itmages.ru/i/16/0915/h_1473945375_7504873_be0fe246e3.jpg", - 9.2, - system_clock::now() - ), - HotelReview::CriticReview( - "Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly.", - "Little bit noise from outside", - "Anonymous2", - "http://storage1.static.itmages.ru/i/16/0915/h_1473945374_9397526_996bbca0d7.jpg", - 9.2, - system_clock::now() - ) - }; - - fn(info); -} - -string BookingApi::MakeApiUrl(string const & func, - initializer_list> const & params) -{ - stringstream ss; - ss << m_apiUrl << func << "?"; - bool firstRun = true; - for (auto const & param : params) - ss << (firstRun ? firstRun = false, "" : "&") << param.first << "=" << param.second; - if (m_testingMode) - ss << "&show_test=1"; - - return ss.str(); + fn(info); + }).detach(); } +} // namespace booking diff --git a/partners_api/booking_api.hpp b/partners_api/booking_api.hpp index 4804fefa7d..e0ddfdd587 100644 --- a/partners_api/booking_api.hpp +++ b/partners_api/booking_api.hpp @@ -1,138 +1,77 @@ #pragma once -#include "platform/http_request.hpp" - #include "std/chrono.hpp" #include "std/function.hpp" -#include "std/initializer_list.hpp" -#include "std/limits.hpp" +#include "std/shared_ptr.hpp" #include "std/string.hpp" -#include "std/unique_ptr.hpp" -#include "std/utility.hpp" +#include "std/vector.hpp" -class BookingApi +namespace booking { - string m_affiliateId; - string m_apiUrl; - bool m_testingMode; +struct HotelPhotoUrls +{ + string m_small; + string m_original; +}; +struct HotelReview +{ + /// An issue date. + time_point m_date; + /// Author's hotel evaluation. + float m_score = 0.0; + /// Review author name. + string m_author; + /// Review text. There can be either one or both positive/negative review. + string m_pros; + string m_cons; +}; + +struct HotelFacility +{ + string m_facilityType; + string m_name; +}; + +struct HotelInfo +{ + string m_hotelId; + + string m_description; + vector m_photos; + vector m_facilities; + vector m_reviews; + float m_score = 0.0; + uint32_t m_scoreCount = 0; +}; + +class RawApi +{ public: - struct HotelPhotoUrls - { - string m_small; - string m_original; - }; + static bool GetHotelAvailability(string const & hotelId, string const & currency, string & result, + bool testing = false); + static bool GetExtendedInfo(string const & hotelId, string const & lang, string & result); +}; - struct HotelReview - { - HotelReview() = default; - // C++11 doesn't allow aggragate initialization for structs with default member initializer. - // But C++14 does. - HotelReview(string const & reviewPositive, - string const & reviewNegative, - string const & reviewNeutral, - string const & author, - string const & authorPictUrl, - float const rating, - time_point const date) - : m_reviewPositive(reviewPositive) - , m_reviewNegative(reviewNegative) - , m_reviewNeutral(reviewNeutral) - , m_author(author) - , m_authorPictUrl(authorPictUrl) - , m_rating(rating) - , m_date(date) - { - } +using GetMinPriceCallback = function; +using GetHotelInfoCallback = function; +class Api +{ +public: + void SetTestingMode(bool testing) { m_testingMode = testing; } - static HotelReview CriticReview(string const & reviewPositive, - string const & reviewNegative, - string const & author, - string const & authorPictUrl, - float const rating, - time_point const date) - { - return { - reviewPositive, - reviewNegative, - "", - author, - authorPictUrl, - rating, - date - }; - } - - static HotelReview NeutralReview(string const & reviewNeutral, - string const & author, - string const & authorPictUrl, - float const rating, - time_point const date) - { - return { - "", - "", - reviewNeutral, - author, - authorPictUrl, - rating, - date - }; - } - - static auto constexpr kInvalidRating = numeric_limits::max(); - - /// Review text. There can be either one or both positive/negative review or - /// a neutral one. - string m_reviewPositive; - string m_reviewNegative; - string m_reviewNeutral; - /// Review author name. - string m_author; - /// Url to a author's picture. - string m_authorPictUrl; - /// Author's hotel evaluation. - float m_rating = kInvalidRating; - /// An issue date. - time_point m_date; - }; - - struct Facility - { - string m_id; - string m_localizedName; - }; - - struct HotelInfo - { - string m_hotelId; - - string m_description; - vector m_photos; - vector m_facilities; - vector m_reviews; - }; - - static constexpr const char kDefaultCurrency[1] = {0}; - - BookingApi(); - string GetBookHotelUrl(string const & baseUrl, string const & lang = string()) const; - string GetDescriptionUrl(string const & baseUrl, string const & lang = string()) const; - inline void SetTestingMode(bool testing) { m_testingMode = testing; } - + string GetBookHotelUrl(string const & baseUrl) const; + string GetDescriptionUrl(string const & baseUrl) const; // Real-time information methods (used for retriving rapidly changing information). // These methods send requests directly to Booking. - void GetMinPrice(string const & hotelId, string const & currency, - function const & fn); - + void GetMinPrice(string const & hotelId, string const & currency, GetMinPriceCallback const & fn); // Static information methods (use for information that can be cached). // These methods use caching server to prevent Booking from being ddossed. - void GetHotelInfo(string const & hotelId, string const & lang, - function const & fn); + void GetHotelInfo(string const & hotelId, string const & lang, GetHotelInfoCallback const & fn); -protected: - unique_ptr m_request; - string MakeApiUrl(string const & func, initializer_list> const & params); +private: + bool m_testingMode = false; }; +} // namespace booking diff --git a/partners_api/partners_api_tests/booking_tests.cpp b/partners_api/partners_api_tests/booking_tests.cpp index 86f3fcf54c..887313635d 100644 --- a/partners_api/partners_api_tests/booking_tests.cpp +++ b/partners_api/partners_api_tests/booking_tests.cpp @@ -2,32 +2,43 @@ #include "partners_api/booking_api.hpp" -UNIT_TEST(Booking_SmokeTest) +namespace { - BookingApi api; +string const kHotelId = "98251"; // Special hotel id for testing. - string url = api.GetBookHotelUrl("http://someurl.com"); - TEST(!url.empty(), ()); +UNIT_TEST(Booking_GetHotelAvailability) +{ + string result; + TEST(booking::RawApi::GetHotelAvailability(kHotelId, "", result, true), ()); + TEST(!result.empty(), ()); +} + +UNIT_TEST(Booking_GetExtendedInfo) +{ + string result; + TEST(booking::RawApi::GetExtendedInfo(kHotelId, "", result), ()); + TEST(!result.empty(), ()); } UNIT_TEST(Booking_GetMinPrice) { - BookingApi api; + booking::Api api; api.SetTestingMode(true); - string const kHotelId = "98251"; // Special hotel id for testing. { string price; string currency; - api.GetMinPrice(kHotelId, BookingApi::kDefaultCurrency, - [&price, ¤cy](string const & val, string const & curr) - { + string hotelId; + api.GetMinPrice(kHotelId, "" /* default currency */, + [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) { + hotelId = id; price = val; currency = curr; testing::StopEventLoop(); }); testing::RunEventLoop(); + TEST_EQUAL(hotelId, kHotelId, ()); TEST(!price.empty(), ()); TEST(!currency.empty(), ()); TEST_EQUAL(currency, "USD", ()); @@ -36,14 +47,17 @@ UNIT_TEST(Booking_GetMinPrice) { string price; string currency; - api.GetMinPrice(kHotelId, "RUB", [&price, ¤cy](string const & val, string const & curr) + string hotelId; + api.GetMinPrice(kHotelId, "RUB", [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) { + hotelId = id; price = val; currency = curr; testing::StopEventLoop(); }); testing::RunEventLoop(); + TEST_EQUAL(hotelId, kHotelId, ()); TEST(!price.empty(), ()); TEST(!currency.empty(), ()); TEST_EQUAL(currency, "RUB", ()); @@ -52,32 +66,37 @@ UNIT_TEST(Booking_GetMinPrice) { string price; string currency; - api.GetMinPrice(kHotelId, "ISK", [&price, ¤cy](string const & val, string const & curr) + string hotelId; + api.GetMinPrice(kHotelId, "ISK", [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) { + hotelId = id; price = val; currency = curr; testing::StopEventLoop(); }); testing::RunEventLoop(); + TEST_EQUAL(hotelId, kHotelId, ()); TEST(!price.empty(), ()); TEST(!currency.empty(), ()); TEST_EQUAL(currency, "ISK", ()); } } -UNIT_TEST(GetHotelInfo) // GetHotelInfo is a mockup now. +UNIT_TEST(GetHotelInfo) { - BookingApi api; - BookingApi::HotelInfo info; + booking::Api api; + booking::HotelInfo info; - api.GetHotelInfo("000", "en", [&info](BookingApi::HotelInfo const & i) + api.GetHotelInfo(kHotelId, "en", [&info](booking::HotelInfo const & i) { info = i; }); + TEST_EQUAL(info.m_hotelId, kHotelId, ()); TEST(!info.m_description.empty(), ()); - TEST_EQUAL(info.m_photos.size(), 9, ()); + TEST_EQUAL(info.m_photos.size(), 5, ()); TEST_EQUAL(info.m_facilities.size(), 3, ()); TEST_EQUAL(info.m_reviews.size(), 12, ()); } +} diff --git a/platform/http_client.cpp b/platform/http_client.cpp index c1f31811bf..cda1229cd1 100644 --- a/platform/http_client.cpp +++ b/platform/http_client.cpp @@ -40,7 +40,7 @@ HttpClient & HttpClient::SetReceivedFile(string const & received_file) HttpClient & HttpClient::SetUserAndPassword(string const & user, string const & password) { - m_headers.emplace("Authorization", "Basic" + base64::Encode(user + ":" + password)); + m_headers.emplace("Authorization", "Basic " + base64::Encode(user + ":" + password)); return *this; } diff --git a/xcode/search/search.xcodeproj/project.pbxproj b/xcode/search/search.xcodeproj/project.pbxproj index ba81f54287..881594a798 100644 --- a/xcode/search/search.xcodeproj/project.pbxproj +++ b/xcode/search/search.xcodeproj/project.pbxproj @@ -61,8 +61,6 @@ 347F33301C4540A8009758CC /* suggest.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 347F33151C4540A8009758CC /* suggest.hpp */; }; 349B65891D4F21E5001798E2 /* lazy_centers_table.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 349B65871D4F21E5001798E2 /* lazy_centers_table.cpp */; }; 349B658A1D4F21E5001798E2 /* lazy_centers_table.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 349B65881D4F21E5001798E2 /* lazy_centers_table.hpp */; }; - 34EEAD701E55AE4300E95575 /* token_range.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 34EEAD6F1E55AE4300E95575 /* token_range.hpp */; }; - 34EEAD721E55AE5C00E95575 /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34EEAD711E55AE5C00E95575 /* utils.cpp */; }; 34F5583B1DBF2E0E00A4FC11 /* libalohalitics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F5583A1DBF2E0E00A4FC11 /* libalohalitics.a */; }; 34F5583D1DBF2E2700A4FC11 /* libeditor.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F5583C1DBF2E2700A4FC11 /* libeditor.a */; }; 34F5583F1DBF2E3400A4FC11 /* libpugixml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F5583E1DBF2E3400A4FC11 /* libpugixml.a */; }; @@ -73,6 +71,8 @@ 34F558491DBF2EC700A4FC11 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F558481DBF2EC700A4FC11 /* libz.tbd */; }; 397AFE061D6C9AC700F583E7 /* downloader_search_callback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 397AFE041D6C9AC700F583E7 /* downloader_search_callback.cpp */; }; 397AFE071D6C9AC700F583E7 /* downloader_search_callback.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 397AFE051D6C9AC700F583E7 /* downloader_search_callback.hpp */; }; + 3DBC1C581E55B22F0016897F /* token_range.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DBC1C561E55B22F0016897F /* token_range.hpp */; }; + 3DBC1C591E55B22F0016897F /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC1C571E55B22F0016897F /* utils.cpp */; }; 56D5456E1C74A48C00E3719C /* mode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 56D5456C1C74A48C00E3719C /* mode.cpp */; }; 56D5456F1C74A48C00E3719C /* mode.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 56D5456D1C74A48C00E3719C /* mode.hpp */; }; 670F88741CE4C032003F68BA /* types_skipper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 670F88721CE4C032003F68BA /* types_skipper.cpp */; }; @@ -240,8 +240,6 @@ 347F33151C4540A8009758CC /* suggest.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = suggest.hpp; sourceTree = ""; }; 349B65871D4F21E5001798E2 /* lazy_centers_table.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lazy_centers_table.cpp; sourceTree = ""; }; 349B65881D4F21E5001798E2 /* lazy_centers_table.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = lazy_centers_table.hpp; sourceTree = ""; }; - 34EEAD6F1E55AE4300E95575 /* token_range.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = token_range.hpp; sourceTree = ""; }; - 34EEAD711E55AE5C00E95575 /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = ""; }; 34F558371DBF2C8B00A4FC11 /* common-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../common-debug.xcconfig"; sourceTree = ""; }; 34F558381DBF2C8B00A4FC11 /* common-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-release.xcconfig"; path = "../common-release.xcconfig"; sourceTree = ""; }; 34F5583A1DBF2E0E00A4FC11 /* libalohalitics.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libalohalitics.a; path = "../../../omim-xcode-build/Debug/libalohalitics.a"; sourceTree = ""; }; @@ -254,6 +252,8 @@ 34F558481DBF2EC700A4FC11 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 397AFE041D6C9AC700F583E7 /* downloader_search_callback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = downloader_search_callback.cpp; sourceTree = ""; }; 397AFE051D6C9AC700F583E7 /* downloader_search_callback.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = downloader_search_callback.hpp; sourceTree = ""; }; + 3DBC1C561E55B22F0016897F /* token_range.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = token_range.hpp; sourceTree = ""; }; + 3DBC1C571E55B22F0016897F /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = ""; }; 56D5456C1C74A48C00E3719C /* mode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mode.cpp; sourceTree = ""; }; 56D5456D1C74A48C00E3719C /* mode.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = mode.hpp; sourceTree = ""; }; 670F88721CE4C032003F68BA /* types_skipper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = types_skipper.cpp; sourceTree = ""; }; @@ -484,6 +484,8 @@ 675346B21A4055CF00A0A8C3 /* search */ = { isa = PBXGroup; children = ( + 3DBC1C561E55B22F0016897F /* token_range.hpp */, + 3DBC1C571E55B22F0016897F /* utils.cpp */, 675346BE1A40560D00A0A8C3 /* algos.hpp */, 675346BF1A40560D00A0A8C3 /* approximate_string_match.cpp */, 675346C01A40560D00A0A8C3 /* approximate_string_match.hpp */, @@ -600,12 +602,10 @@ 345C8DAE1D2D15A50037E3A6 /* streets_matcher.hpp */, F652D8E61CFDE21900FC29A0 /* string_intersection.hpp */, 347F33151C4540A8009758CC /* suggest.hpp */, - 34EEAD6F1E55AE4300E95575 /* token_range.hpp */, F652D8E71CFDE21900FC29A0 /* token_slice.cpp */, F652D8E81CFDE21900FC29A0 /* token_slice.hpp */, 670F88721CE4C032003F68BA /* types_skipper.cpp */, 670F88731CE4C032003F68BA /* types_skipper.hpp */, - 34EEAD711E55AE5C00E95575 /* utils.cpp */, 3461C9A11D79949600E6E6F5 /* utils.hpp */, 3465B27F1D5DE71A0021E14D /* viewport_search_callback.cpp */, 3465B2801D5DE71A0021E14D /* viewport_search_callback.hpp */, @@ -636,7 +636,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 34EEAD701E55AE4300E95575 /* token_range.hpp in Headers */, 3461C9A51D79949600E6E6F5 /* utils.hpp in Headers */, 345C8DB01D2D15A50037E3A6 /* cbv.hpp in Headers */, F652D90C1CFDE21900FC29A0 /* street_vicinity_loader.hpp in Headers */, @@ -840,7 +839,6 @@ F652D8F01CFDE21900FC29A0 /* geocoder.cpp in Sources */, F652D8F21CFDE21900FC29A0 /* geometry_cache.cpp in Sources */, 34586B8C1DCB1E8300CF7FC9 /* locality_scorer_test.cpp in Sources */, - 34EEAD721E55AE5C00E95575 /* utils.cpp in Sources */, 34586B8E1DCB1E8300CF7FC9 /* nearby_points_sweeper_test.cpp in Sources */, 345C8DB11D2D15A50037E3A6 /* geocoder_context.cpp in Sources */, 3461C9A31D79949600E6E6F5 /* editor_delegate.cpp in Sources */,