diff --git a/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp b/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp index 53ff373b98..9f31ed6299 100644 --- a/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp +++ b/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp @@ -50,6 +50,8 @@ jmethodID g_catalogCustomPropertyConstructor; jmethodID g_onPingFinishedMethod; +jmethodID g_onCheckInvalidCategoriesMethod; + void PrepareClassRefs(JNIEnv * env) { if (g_bookmarkManagerClass) @@ -101,6 +103,9 @@ void PrepareClassRefs(JNIEnv * env) g_onPingFinishedMethod = jni::GetMethodID(env, bookmarkManagerInstance, "onPingFinished", "(Z)V"); + g_onCheckInvalidCategoriesMethod = jni::GetMethodID(env, bookmarkManagerInstance, + "onCheckInvalidCategories", "(Z)V"); + g_bookmarkCategoryClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/bookmarks/data/BookmarkCategory"); //public BookmarkCategory(long id, @@ -368,6 +373,17 @@ void OnPingFinished(JNIEnv * env, bool isSuccessful) jni::HandleJavaException(env); } +void OnCheckInvalidCategories(JNIEnv * env, bool hasInvalidCategories) +{ + ASSERT(g_bookmarkManagerClass, ()); + + auto bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + env->CallVoidMethod(bookmarkManagerInstance, g_onCheckInvalidCategoriesMethod, + static_cast(hasInvalidCategories)); + jni::HandleJavaException(env); +} + void OnUploadStarted(JNIEnv * env, kml::MarkGroupId originCategoryId) { ASSERT(g_bookmarkManagerClass, ()); @@ -936,8 +952,34 @@ Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativePingBookmarkCatalo }); } +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeCheckInvalidCategories(JNIEnv * env, + jobject) +{ + frm()->GetBookmarkManager().CheckInvalidCategories(Purchase::GetDeviceId(), + [env](bool hasInvalidCategories) + { + OnCheckInvalidCategories(env, hasInvalidCategories); + }); +} + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeDeleteInvalidCategories(JNIEnv * env, + jobject) +{ + frm()->GetBookmarkManager().DeleteInvalidCategories(); +} + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeResetInvalidCategories(JNIEnv * env, + jobject) +{ + frm()->GetBookmarkManager().ResetInvalidCategories(); +} + JNIEXPORT jobjectArray JNICALL -Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeGetBookmarkCategories(JNIEnv *env, jobject thiz) +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeGetBookmarkCategories(JNIEnv *env, + jobject thiz) { auto const & bm = frm()->GetBookmarkManager(); kml::GroupIdCollection const & categories = bm.GetBmGroupsIdList(); diff --git a/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java b/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java index 0491979646..f187f6639a 100644 --- a/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java +++ b/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java @@ -69,6 +69,9 @@ public enum BookmarkManager @NonNull private final List mCatalogPingListeners = new ArrayList<>(); + + @NonNull + private final List mInvalidCategoriesListeners = new ArrayList<>(); static { @@ -327,6 +330,15 @@ public enum BookmarkManager listener.onPingFinished(isServiceAvailable); } + // Called from JNI. + @SuppressWarnings("unused") + @MainThread + public void onCheckInvalidCategories(boolean hasInvalidCategories) + { + for (BookmarksInvalidCategoriesListener listener : mInvalidCategoriesListeners) + listener.onCheckInvalidCategories(hasInvalidCategories); + } + public boolean isVisible(long catId) { return nativeIsVisible(catId); @@ -661,6 +673,21 @@ public enum BookmarkManager nativePingBookmarkCatalog(); } + public void checkInvalidCategories() + { + nativeCheckInvalidCategories(); + } + + public void deleteInvalidCategories() + { + nativeDeleteInvalidCategories(); + } + + public void resetInvalidCategories() + { + nativeResetInvalidCategories(); + } + public boolean isCategoryFromCatalog(long catId) { return nativeIsCategoryFromCatalog(catId); @@ -805,6 +832,10 @@ public enum BookmarkManager private static native void nativePingBookmarkCatalog(); + private static native void nativeCheckInvalidCategories(); + private static native void nativeDeleteInvalidCategories(); + private static native void nativeResetInvalidCategories(); + public interface BookmarksLoadingListener { void onBookmarksLoadingStarted(); @@ -865,6 +896,11 @@ public enum BookmarkManager void onPingFinished(boolean isServiceAvailable); } + public interface BookmarksInvalidCategoriesListener + { + void onCheckInvalidCategories(boolean hasInvalidCategories); + } + public interface BookmarksCatalogListener { /** diff --git a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm index 89ceb51ceb..36868dd7c1 100644 --- a/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm +++ b/iphone/Maps/Core/Bookmarks/MWMBookmarksManager.mm @@ -10,6 +10,8 @@ #include "Framework.h" +#include "map/purchase.hpp" + #include "partners_api/utm.hpp" #include "coding/internal/file_data.hpp" @@ -592,7 +594,7 @@ NSString * const CloudErrorToString(Cloud::SynchronizationResult result) observer.progressBlock = progress; observer.downloadCompletionBlock = completion; [self.catalogObservers setObject:observer forKey:itemId]; - self.bm.DownloadFromCatalogAndImport(itemId.UTF8String, name.UTF8String); + self.bm.DownloadFromCatalogAndImport(itemId.UTF8String, Purchase::GetDeviceId(), name.UTF8String); } - (void)uploadAndGetDirectLinkCategoryWithId:(MWMMarkGroupID)itemId diff --git a/map/bookmark_catalog.cpp b/map/bookmark_catalog.cpp index 1d3f37cb34..a76c42a83c 100644 --- a/map/bookmark_catalog.cpp +++ b/map/bookmark_catalog.cpp @@ -77,6 +77,13 @@ std::string BuildPingUrl() return kCatalogFrontendServer + "storage/ping"; } +std::string BuildDeleteRequestUrl() +{ + if (kCatalogFrontendServer.empty()) + return {}; + return kCatalogFrontendServer + "storage/kmls_to_delete"; +} + struct SubtagData { std::string m_name; @@ -157,6 +164,31 @@ struct HashResponseData DECLARE_VISITOR(visitor(m_hash, "hash")) }; +struct DeleteRequestData +{ + DeleteRequestData(std::string const & deviceId, std::string const & userId, + std::vector const & serverIds) + : m_deviceId(deviceId) + , m_userId(userId) + , m_serverIds(serverIds) + {} + + std::string m_deviceId; + std::string m_userId; + std::vector m_serverIds; + + DECLARE_VISITOR(visitor(m_deviceId, "server_id"), + visitor(m_userId, "user_id"), + visitor(m_serverIds, "server_ids")) +}; + +struct DeleteRequestResponseData +{ + std::vector m_serverIds; + + DECLARE_VISITOR(visitor(m_serverIds)) +}; + int constexpr kInvalidHash = -1; int RequestNewServerId(std::string const & accessToken, @@ -223,7 +255,7 @@ bool BookmarkCatalog::HasDownloaded(std::string const & id) const } void BookmarkCatalog::Download(std::string const & id, std::string const & accessToken, - DownloadStartCallback && startHandler, + std::string const & deviceId, DownloadStartCallback && startHandler, DownloadFinishCallback && finishHandler) { if (IsDownloading(id) || HasDownloaded(id)) @@ -234,7 +266,7 @@ void BookmarkCatalog::Download(std::string const & id, std::string const & acces static uint32_t counter = 0; auto const path = base::JoinPath(GetPlatform().TmpDir(), "file" + strings::to_string(++counter)); - platform::RemoteFile remoteFile(BuildCatalogDownloadUrl(id), accessToken); + platform::RemoteFile remoteFile(BuildCatalogDownloadUrl(id), accessToken, deviceId); remoteFile.DownloadAsync(path, [startHandler = std::move(startHandler)](std::string const &) { if (startHandler) @@ -630,6 +662,67 @@ void BookmarkCatalog::Ping(PingCallback && callback) const }); } +void BookmarkCatalog::RequestBookmarksToDelete(std::string const & accessToken, std::string const & userId, + std::string const & deviceId, std::vector const & serverIds, + BookmarksToDeleteCallback && callback) const +{ + auto const url = BuildDeleteRequestUrl(); + if (url.empty()) + { + if (callback) + callback({}); + return; + } + + GetPlatform().RunTask(Platform::Thread::Network, + [url, accessToken, userId, deviceId, serverIds, callback = std::move(callback)]() + { + platform::HttpClient request(url); + request.SetRawHeader("Accept", "application/json"); + request.SetRawHeader("User-Agent", GetPlatform().GetAppUserAgent()); + if (!accessToken.empty()) + request.SetRawHeader("Authorization", "Bearer " + accessToken); + request.SetHttpMethod("POST"); + + std::string jsonStr; + { + using Sink = MemWriter; + Sink sink(jsonStr); + coding::SerializerJson serializer(sink); + serializer(DeleteRequestData(deviceId, userId, serverIds)); + } + request.SetBodyData(std::move(jsonStr), "application/json"); + + uint32_t constexpr kTimeoutInSec = 5; + request.SetTimeout(kTimeoutInSec); + if (request.RunHttpRequest()) + { + auto const resultCode = request.ErrorCode(); + if (callback && resultCode >= 200 && resultCode < 300) + { + DeleteRequestResponseData responseData; + try + { + coding::DeserializerJson des(request.ServerResponse()); + des(responseData); + } + catch (coding::DeserializerJson::Exception const & ex) + { + LOG(LWARNING, ("Bookmarks-to-delete request deserialization error:", ex.Msg())); + if (callback) + callback({}); + return; + } + + callback(responseData.m_serverIds); + return; + } + } + if (callback) + callback({}); + }); +} + void BookmarkCatalog::SetInvalidTokenHandler(InvalidTokenHandler && onInvalidToken) { m_onInvalidToken = std::move(onInvalidToken); diff --git a/map/bookmark_catalog.hpp b/map/bookmark_catalog.hpp index 192f58b65c..68bf5dc14b 100644 --- a/map/bookmark_catalog.hpp +++ b/map/bookmark_catalog.hpp @@ -69,7 +69,7 @@ public: using DownloadFinishCallback = std::function; - void Download(std::string const & id, std::string const & accessToken, + void Download(std::string const & id, std::string const & accessToken, std::string const & deviceId, DownloadStartCallback && startHandler, DownloadFinishCallback && finishHandler); bool IsDownloading(std::string const & id) const; @@ -121,6 +121,11 @@ public: // Handler can be called from non-UI thread. void SetInvalidTokenHandler(InvalidTokenHandler && onInvalidToken); + using BookmarksToDeleteCallback = platform::SafeCallback const & serverIds)>; + void RequestBookmarksToDelete(std::string const & accessToken, std::string const & userId, + std::string const & deviceId, std::vector const & serverIds, + BookmarksToDeleteCallback && callback) const; + private: std::set m_downloadingIds; std::set m_registeredInCatalog; diff --git a/map/bookmark_manager.cpp b/map/bookmark_manager.cpp index 331b9f7ccd..85812a8bd0 100644 --- a/map/bookmark_manager.cpp +++ b/map/bookmark_manager.cpp @@ -1995,6 +1995,19 @@ void BookmarkManager::SetAllCategoriesVisibility(CategoryFilterType const filter } } +std::vector BookmarkManager::GetAllPaidCategoriesIds() const +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + std::vector ids; + for (auto const & category : m_categories) + { + if (category.second->GetCategoryData().m_accessRules != kml::AccessRules::Paid) + continue; + ids.emplace_back(category.second->GetServerId()); + } + return ids; +} + bool BookmarkManager::CanConvert() const { // The conversion available only after successful migration. @@ -2239,9 +2252,10 @@ void BookmarkManager::SetCatalogHandlers(OnCatalogDownloadStartedHandler && onCa m_onCatalogUploadFinishedHandler = std::move(onCatalogUploadFinishedHandler); } -void BookmarkManager::DownloadFromCatalogAndImport(std::string const & id, std::string const & name) +void BookmarkManager::DownloadFromCatalogAndImport(std::string const & id, std::string const & deviceId, + std::string const & name) { - m_bookmarkCatalog.Download(id, m_user.GetAccessToken(), [this, id]() + m_bookmarkCatalog.Download(id, m_user.GetAccessToken(), deviceId, [this, id]() { if (m_onCatalogDownloadStarted) m_onCatalogDownloadStarted(id); @@ -2504,6 +2518,47 @@ void BookmarkManager::EnableTestMode(bool enable) m_testModeEnabled = enable; } +void BookmarkManager::CheckInvalidCategories(std::string const & deviceId, + CheckInvalidCategoriesHandler && handler) +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + m_bookmarkCatalog.RequestBookmarksToDelete(m_user.GetAccessToken(), m_user.GetUserId(), deviceId, + GetAllPaidCategoriesIds(), + [this, handler = std::move(handler)]( + std::vector const & serverIds) + { + CHECK_THREAD_CHECKER(m_threadChecker, ()); + m_invalidCategories.clear(); + for (auto const & s : serverIds) + { + for (auto const & category : m_categories) + { + if (category.second->GetServerId() == s) + m_invalidCategories.emplace_back(category.first); + } + } + if (handler) + handler(!m_invalidCategories.empty()); + }); +} + +void BookmarkManager::DeleteInvalidCategories() +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + if (m_invalidCategories.empty()) + return; + + auto session = GetEditSession(); + for (auto const markGroupId : m_invalidCategories) + session.DeleteBmCategory(markGroupId); +} + +void BookmarkManager::ResetInvalidCategories() +{ + CHECK_THREAD_CHECKER(m_threadChecker, ()); + m_invalidCategories.clear(); +} + kml::GroupIdSet BookmarkManager::MarksChangesTracker::GetAllGroupIds() const { auto const & groupIds = m_bmManager.GetBmGroupsIdList(); diff --git a/map/bookmark_manager.hpp b/map/bookmark_manager.hpp index a384e2b487..d9418c78d9 100644 --- a/map/bookmark_manager.hpp +++ b/map/bookmark_manager.hpp @@ -319,7 +319,8 @@ public: OnCatalogImportFinishedHandler && onCatalogImportFinished, OnCatalogUploadStartedHandler && onCatalogUploadStartedHandler, OnCatalogUploadFinishedHandler && onCatalogUploadFinishedHandler); - void DownloadFromCatalogAndImport(std::string const & id, std::string const & name); + void DownloadFromCatalogAndImport(std::string const & id, std::string const & deviceId, + std::string const & name); void ImportDownloadedFromCatalog(std::string const & id, std::string const & filePath); void UploadToCatalog(kml::MarkGroupId categoryId, kml::AccessRules accessRules); bool IsCategoryFromCatalog(kml::MarkGroupId categoryId) const; @@ -329,6 +330,11 @@ public: bool IsMyCategory(kml::MarkGroupId categoryId) const; + using CheckInvalidCategoriesHandler = std::function; + void CheckInvalidCategories(std::string const & deviceId, CheckInvalidCategoriesHandler && handler); + void DeleteInvalidCategories(); + void ResetInvalidCategories(); + /// These functions are public for unit tests only. You shouldn't call them from client code. void EnableTestMode(bool enable); bool SaveBookmarkCategory(kml::MarkGroupId groupId); @@ -531,6 +537,9 @@ private: bool HasDuplicatedIds(kml::FileData const & fileData) const; bool CheckVisibility(CategoryFilterType const filter, bool isVisible) const; + + std::vector GetAllPaidCategoriesIds() const; + ThreadChecker m_threadChecker; User & m_user; @@ -597,6 +606,8 @@ private: }; std::map m_restoringCache; + std::vector m_invalidCategories; + bool m_testModeEnabled = false; DISALLOW_COPY_AND_MOVE(BookmarkManager); diff --git a/map/cloud.cpp b/map/cloud.cpp index 48fdf9119e..149ba95063 100644 --- a/map/cloud.cpp +++ b/map/cloud.cpp @@ -1397,7 +1397,8 @@ void Cloud::DownloadingTask(std::string const & dirPath, bool useFallbackUrl, platform::RemoteFile remoteFile(useFallbackUrl ? result.m_response.m_fallbackUrl : result.m_response.m_url, - {} /* accessToken */, false /* allowRedirection */); + {} /* accessToken */, {} /* deviceId */, + false /* allowRedirection */); auto const downloadResult = remoteFile.Download(filePath); if (downloadResult.m_status == platform::RemoteFile::Status::Ok) diff --git a/map/purchase.cpp b/map/purchase.cpp index 6c3c027b3a..b1989c069e 100644 --- a/map/purchase.cpp +++ b/map/purchase.cpp @@ -46,11 +46,6 @@ std::string GetSubscriptionId(SubscriptionType type) kSubscriptionSuffix[base::Underlying(type)]); } -std::string GetDeviceId() -{ - return coding::SHA1::CalculateBase64ForString(GetPlatform().UniqueClientId()); -} - std::string GetSubscriptionKey(SubscriptionType type) { return kSubscriptionId + kSubscriptionSuffix[base::Underlying(type)]; @@ -309,3 +304,9 @@ void Purchase::ValidateImpl(std::string const & url, ValidationInfo const & vali }); } } + +// static +std::string Purchase::GetDeviceId() +{ + return coding::SHA1::CalculateBase64ForString(GetPlatform().UniqueClientId()); +} diff --git a/map/purchase.hpp b/map/purchase.hpp index ab25b4f92e..108f0b6a10 100644 --- a/map/purchase.hpp +++ b/map/purchase.hpp @@ -65,6 +65,8 @@ public: void StartTransaction(std::string const & serverId, std::string const & vendorId, std::string const & accessToken); + static std::string GetDeviceId(); + private: void ValidateImpl(std::string const & url, ValidationInfo const & validationInfo, std::string const & accessToken, bool startTransaction, diff --git a/platform/remote_file.cpp b/platform/remote_file.cpp index 483a5a14fb..72cdf6c27f 100644 --- a/platform/remote_file.cpp +++ b/platform/remote_file.cpp @@ -15,9 +15,10 @@ double constexpr kRequestTimeoutInSec = 5.0; } // namespace RemoteFile::RemoteFile(std::string url, std::string accessToken /* = {} */, - bool allowRedirection /* = true */) + std::string deviceId /* = {} */, bool allowRedirection /* = true */) : m_url(std::move(url)) , m_accessToken(std::move(accessToken)) + , m_deviceId(std::move(deviceId)) , m_allowRedirection(allowRedirection) {} @@ -30,6 +31,8 @@ RemoteFile::Result RemoteFile::Download(std::string const & filePath) const request.SetTimeout(kRequestTimeoutInSec); if (!m_accessToken.empty()) request.SetRawHeader("Authorization", "Bearer " + m_accessToken); + if (!m_deviceId.empty()) + request.SetRawHeader("X-Mapsme-Device-Id", m_deviceId); request.SetRawHeader("User-Agent", GetPlatform().GetAppUserAgent()); if (request.RunHttpRequest()) { diff --git a/platform/remote_file.hpp b/platform/remote_file.hpp index 346e5d97e0..7bca44c9d1 100644 --- a/platform/remote_file.hpp +++ b/platform/remote_file.hpp @@ -36,7 +36,8 @@ public: {} }; - explicit RemoteFile(std::string url, std::string accessToken = {}, bool allowRedirection = true); + explicit RemoteFile(std::string url, std::string accessToken = {}, std::string deviceId = {}, + bool allowRedirection = true); Result Download(std::string const & filePath) const; @@ -49,6 +50,7 @@ public: private: std::string const m_url; std::string const m_accessToken; + std::string const m_deviceId; bool const m_allowRedirection; }; } // namespace platform