diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index ed937d10cc..6a2ff91add 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -957,12 +957,6 @@ Java_com_mapswithme_maps_Framework_nativeSetWritableDir(JNIEnv * env, jclass, js g_framework->AddLocalMaps(); } -JNIEXPORT void JNICALL -Java_com_mapswithme_maps_Framework_nativeLoadBookmarks(JNIEnv * env, jclass) -{ - frm()->LoadBookmarks(); -} - JNIEXPORT jboolean JNICALL Java_com_mapswithme_maps_Framework_nativeIsRoutingActive(JNIEnv * env, jclass) { diff --git a/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp b/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp index bd5225a739..97c7b714fc 100644 --- a/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp +++ b/android/jni/com/mapswithme/maps/bookmarks/data/BookmarkManager.cpp @@ -7,9 +7,81 @@ #include "coding/zip_creator.hpp" #include "map/place_page_info.hpp" +#include + +using namespace std::placeholders; + namespace { ::Framework * frm() { return g_framework->NativeFramework(); } + +jclass g_bookmarkManagerClass; +jfieldID g_bookmarkManagerInstanceField; +jmethodID g_onBookmarksLoadingStartedMethod; +jmethodID g_onBookmarksLoadingFinishedMethod; +jmethodID g_onBookmarksLoadingFileMethod; + +void PrepareClassRefs(JNIEnv * env) +{ + if (g_bookmarkManagerClass) + return; + + g_bookmarkManagerClass = + jni::GetGlobalClassRef(env, "com/mapswithme/maps/bookmarks/data/BookmarkManager"); + g_bookmarkManagerInstanceField = jni::GetStaticFieldID(env, g_bookmarkManagerClass, "INSTANCE", + "Lcom/mapswithme/maps/bookmarks/data/BookmarkManager;"); + + jobject bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + g_onBookmarksLoadingStartedMethod = + jni::GetMethodID(env, bookmarkManagerInstance, "onBookmarksLoadingStarted", "()V"); + g_onBookmarksLoadingFinishedMethod = + jni::GetMethodID(env, bookmarkManagerInstance, "onBookmarksLoadingFinished", "()V"); + g_onBookmarksLoadingFileMethod = + jni::GetMethodID(env, bookmarkManagerInstance, "onBookmarksLoadingFile", + "(ZLjava/lang/String;Z)V"); +} + +void OnAsyncLoadingStarted(JNIEnv * env) +{ + ASSERT(g_bookmarkManagerClass != nullptr, ()); + LOG(LINFO, ("!!!!!!")); + jobject bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + env->CallVoidMethod(bookmarkManagerInstance, g_onBookmarksLoadingStartedMethod); + jni::HandleJavaException(env); +} + +void OnAsyncLoadingFinished(JNIEnv * env) +{ + ASSERT(g_bookmarkManagerClass != nullptr, ()); + jobject bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + env->CallVoidMethod(bookmarkManagerInstance, g_onBookmarksLoadingFinishedMethod); + jni::HandleJavaException(env); +} + +void OnAsyncLoadingFileSuccess(JNIEnv * env, std::string const & fileName, bool isTemporaryFile) +{ + ASSERT(g_bookmarkManagerClass != nullptr, ()); + jobject bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + jni::TScopedLocalRef jFileName(env, jni::ToJavaString(env, fileName)); + env->CallVoidMethod(bookmarkManagerInstance, g_onBookmarksLoadingFileMethod, + true /* success */, jFileName.get(), isTemporaryFile); + jni::HandleJavaException(env); +} + +void OnAsyncLoadingFileError(JNIEnv * env, std::string const & fileName, bool isTemporaryFile) +{ + ASSERT(g_bookmarkManagerClass != nullptr, ()); + jobject bookmarkManagerInstance = env->GetStaticObjectField(g_bookmarkManagerClass, + g_bookmarkManagerInstanceField); + jni::TScopedLocalRef jFileName(env, jni::ToJavaString(env, fileName)); + env->CallVoidMethod(bookmarkManagerInstance, g_onBookmarksLoadingFileMethod, + false /* success */, jFileName.get(), isTemporaryFile); + jni::HandleJavaException(env); +} } // namespace namespace bookmarks_helper @@ -39,8 +111,16 @@ Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeShowBookmarkOnMap( } JNIEXPORT void JNICALL -Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeLoadBookmarks(JNIEnv * env, jobject thiz) +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeLoadBookmarks(JNIEnv * env, jobject) { + PrepareClassRefs(env); + BookmarkManager::AsyncLoadingCallbacks callbacks; + callbacks.m_onStarted = std::bind(&OnAsyncLoadingStarted, env); + callbacks.m_onFinished = std::bind(&OnAsyncLoadingFinished, env); + callbacks.m_onFileSuccess = std::bind(&OnAsyncLoadingFileSuccess, env, _1, _2); + callbacks.m_onFileError = std::bind(&OnAsyncLoadingFileError, env, _1, _2); + frm()->GetBookmarkManager().SetAsyncLoadingCallbacks(std::move(callbacks)); + frm()->LoadBookmarks(); } @@ -118,7 +198,7 @@ Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeAddBookmarkToLastE } JNIEXPORT jint JNICALL -Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_getLastEditedCategory( +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeGetLastEditedCategory( JNIEnv * env, jobject thiz) { return frm()->LastEditedBMCategory(); @@ -132,10 +212,11 @@ Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeGenerateUniqueFile return ToJavaString(env, bookmarkFileName); } -JNIEXPORT jboolean JNICALL -Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeLoadKmzFile(JNIEnv * env, jobject thiz, jstring path) +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeLoadKmzFile(JNIEnv * env, jobject thiz, + jstring path, jboolean isTemporaryFile) { - return frm()->AddBookmarksFile(ToNativeString(env, path)); + frm()->AddBookmarksFile(ToNativeString(env, path), isTemporaryFile); } JNIEXPORT jstring JNICALL @@ -143,4 +224,11 @@ Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeFormatNewBookmarkN { return ToJavaString(env, g_framework->GetPlacePageInfo().FormatNewBookmarkName()); } + +JNIEXPORT jboolean JNICALL +Java_com_mapswithme_maps_bookmarks_data_BookmarkManager_nativeIsAsyncBookmarksLoadingInProgress(JNIEnv * env, jclass) +{ + return static_cast(frm()->GetBookmarkManager().IsAsyncLoadingInProgress()); +} + } // extern "C" diff --git a/android/src/com/mapswithme/maps/DownloadResourcesLegacyActivity.java b/android/src/com/mapswithme/maps/DownloadResourcesLegacyActivity.java index 778176be81..9009494eb6 100644 --- a/android/src/com/mapswithme/maps/DownloadResourcesLegacyActivity.java +++ b/android/src/com/mapswithme/maps/DownloadResourcesLegacyActivity.java @@ -73,7 +73,6 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity private String mCurrentCountry; private MapTask mMapTaskToForward; - private boolean mIsReadingAttachment; private boolean mAreResourcesDownloaded; private static final int DOWNLOAD = 0; @@ -433,7 +432,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity private void showMap() { - if (mIsReadingAttachment || !mAreResourcesDownloaded) + if (!mAreResourcesDownloaded) return; final Intent intent = new Intent(this, MwmActivity.class); @@ -734,20 +733,17 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity @Override public boolean process(Intent intent) { - mIsReadingAttachment = true; ThreadPool.getStorage().execute(new Runnable() { @Override public void run() { - final boolean result = readKmzFromIntent(); + readKmzFromIntent(); runOnUiThread(new Runnable() { @Override public void run() { - Utils.toastShortcut(DownloadResourcesLegacyActivity.this, result ? R.string.load_kmz_successful : R.string.load_kmz_failed); - mIsReadingAttachment = false; showMap(); } }); @@ -756,10 +752,10 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity return true; } - private boolean readKmzFromIntent() + private void readKmzFromIntent() { String path = null; - File tmpFile = null; + boolean isTemporaryFile = false; final String scheme = mData.getScheme(); if (scheme != null && !scheme.equalsIgnoreCase(ContentResolver.SCHEME_FILE)) { @@ -775,7 +771,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity { final String filePath = MwmApplication.get().getTempPath() + "Attachment" + ext; - tmpFile = new File(filePath); + File tmpFile = new File(filePath); output = new FileOutputStream(tmpFile); input = resolver.openInputStream(mData); @@ -786,6 +782,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity output.flush(); path = filePath; + isTemporaryFile = true; } } catch (final Exception ex) { @@ -799,20 +796,13 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity else path = mData.getPath(); - boolean result = false; if (path != null) { LOGGER.d(TAG, "Loading bookmarks file from: " + path); - result = BookmarkManager.nativeLoadKmzFile(path); + BookmarkManager.nativeLoadKmzFile(path, isTemporaryFile); } else LOGGER.w(TAG, "Can't get bookmarks file from URI: " + mData); - - if (tmpFile != null) - //noinspection ResultOfMethodCallIgnored - tmpFile.delete(); - - return result; } private String getExtensionFromMime(String mime) diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java index c5d20130b9..f14e2f311c 100644 --- a/android/src/com/mapswithme/maps/Framework.java +++ b/android/src/com/mapswithme/maps/Framework.java @@ -13,7 +13,6 @@ import com.mapswithme.maps.ads.LocalAdInfo; import com.mapswithme.maps.api.ParsedRoutingData; import com.mapswithme.maps.api.ParsedSearchRequest; import com.mapswithme.maps.api.ParsedUrlMwmRequest; -import com.mapswithme.maps.api.RoutePoint; import com.mapswithme.maps.bookmarks.data.DistanceAndAzimut; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.location.LocationHelper; @@ -235,8 +234,6 @@ public class Framework public static native void nativeSetWritableDir(String newPath); - public static native void nativeLoadBookmarks(); - // Routing. public static native boolean nativeIsRoutingActive(); diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index 81a4845a11..7681d49ed6 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -104,6 +104,7 @@ import com.mapswithme.util.statistics.AlohaHelper; import com.mapswithme.util.statistics.PlacePageTracker; import com.mapswithme.util.statistics.Statistics; +import java.io.File; import java.io.Serializable; import java.util.Locale; import java.util.Stack; @@ -122,7 +123,8 @@ public class MwmActivity extends BaseMwmFragmentActivity NativeSearchListener, NavigationButtonsAnimationController.OnTranslationChangedListener, RoutingPlanInplaceController.RoutingPlanListener, - RoutingBottomMenuListener + RoutingBottomMenuListener, + BookmarkManager.BookmarksLoadingListener { public static final String EXTRA_TASK = "map_task"; public static final String EXTRA_LAUNCH_BY_DEEP_LINK = "launch_by_deep_link"; @@ -503,6 +505,8 @@ public class MwmActivity extends BaseMwmFragmentActivity Statistics.INSTANCE.trackConnectionState(); + BookmarkManager.INSTANCE.addListener(this); + mSearchController = new FloatingSearchToolbarController(this); mSearchController.setVisibilityListener(this); SearchEngine.INSTANCE.addListener(this); @@ -919,6 +923,9 @@ public class MwmActivity extends BaseMwmFragmentActivity BottomSheetHelper.free(); SearchEngine.INSTANCE.removeListener(this); + + BookmarkManager.INSTANCE.removeListener(this); + super.onDestroy(); } @@ -2199,6 +2206,25 @@ public class MwmActivity extends BaseMwmFragmentActivity } } + @Override + public void onBookmarksLoadingStarted() + { + // Do nothing + } + + @Override + public void onBookmarksLoadingFinished() + { + // Do nothing + } + + @Override + public void onBookmarksLoadingFile(boolean success) + { + Utils.toastShortcut(MwmActivity.this, success ? R.string.load_kmz_successful : + R.string.load_kmz_failed); + } + public static class ShowAuthorizationTask implements MapTask { @Override diff --git a/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java b/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java index 5dd519822e..f4a2fa3d5c 100644 --- a/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java +++ b/android/src/com/mapswithme/maps/bookmarks/data/BookmarkManager.java @@ -1,20 +1,27 @@ package com.mapswithme.maps.bookmarks.data; +import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import java.io.File; import java.util.ArrayList; import java.util.List; import com.mapswithme.maps.R; import com.mapswithme.util.statistics.Statistics; -public enum BookmarkManager +@MainThread +public class BookmarkManager { - INSTANCE; + public static final BookmarkManager INSTANCE = new BookmarkManager(); public static final List ICONS = new ArrayList<>(); + private List mListeners = new ArrayList<>(); + + private BookmarkManager() {} + static { ICONS.add(new Icon("placemark-red", "placemark-red", R.drawable.ic_bookmark_marker_red_off, R.drawable.ic_bookmark_marker_red_on)); @@ -28,7 +35,7 @@ public enum BookmarkManager ICONS.add(new Icon("placemark-hotel", "placemark-hotel", R.drawable.ic_bookmark_marker_hotel_off, R.drawable.ic_bookmark_marker_hotel_on)); } - public static Icon getIconByType(String type) + static Icon getIconByType(String type) { for (Icon icon : ICONS) { @@ -75,6 +82,47 @@ public enum BookmarkManager return bookmark; } + public void addListener(BookmarksLoadingListener listener) + { + mListeners.add(listener); + } + + public void removeListener(BookmarksLoadingListener listener) + { + mListeners.remove(listener); + } + + // Called from JNI. + @MainThread + public void onBookmarksLoadingStarted() + { + for (BookmarksLoadingListener listener : mListeners) + listener.onBookmarksLoadingStarted(); + } + + // Called from JNI. + @MainThread + public void onBookmarksLoadingFinished() + { + for (BookmarksLoadingListener listener : mListeners) + listener.onBookmarksLoadingFinished(); + } + + // Called from JNI. + @MainThread + public void onBookmarksLoadingFile(boolean success, String fileName, boolean isTemporaryFile) + { + // Android could create temporary file with bookmarks in some cases. Here we can delete it. + if (isTemporaryFile) + { + File tmpFile = new File(fileName); + tmpFile.delete(); + } + + for (BookmarksLoadingListener listener : mListeners) + listener.onBookmarksLoadingFile(success); + } + public static native void nativeLoadBookmarks(); private native void nativeDeleteTrack(int catId, int trackId); @@ -103,7 +151,17 @@ public enum BookmarkManager public static native String nativeGenerateUniqueFileName(String baseName); - public static native boolean nativeLoadKmzFile(String path); + public static native void nativeLoadKmzFile(String path, boolean isTemporaryFile); public static native String nativeFormatNewBookmarkName(); + + public static native boolean nativeIsAsyncBookmarksLoadingInProgress(); + + public interface BookmarksLoadingListener + { + void onBookmarksLoadingStarted(); + void onBookmarksLoadingFinished(); + + void onBookmarksLoadingFile(boolean success); + } } diff --git a/base/worker_thread.cpp b/base/worker_thread.cpp index c42dff9364..1909972d7a 100644 --- a/base/worker_thread.cpp +++ b/base/worker_thread.cpp @@ -13,9 +13,7 @@ WorkerThread::WorkerThread() WorkerThread::~WorkerThread() { - ASSERT(m_checker.CalledOnOriginalThread(), ()); - Shutdown(Exit::SkipPending); - m_thread.join(); + ShutdownAndJoin(); } bool WorkerThread::Push(Task && t) @@ -139,4 +137,12 @@ bool WorkerThread::Shutdown(Exit e) m_cv.notify_one(); return true; } + +void WorkerThread::ShutdownAndJoin() +{ + ASSERT(m_checker.CalledOnOriginalThread(), ()); + Shutdown(Exit::SkipPending); + if (m_thread.joinable()) + m_thread.join(); +} } // namespace base diff --git a/base/worker_thread.hpp b/base/worker_thread.hpp index 1408e89c0c..9b14b9f162 100644 --- a/base/worker_thread.hpp +++ b/base/worker_thread.hpp @@ -62,6 +62,9 @@ public: // thread was shut down previously. bool Shutdown(Exit e); + // Sends a signal to the thread to shut down and waits for completion. + void ShutdownAndJoin(); + TimePoint Now() const { return Clock::now(); } private: diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 354304b926..0037d57a53 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -354,8 +354,25 @@ BOOL gIsFirstMyPositionMode = YES; // May be better solution would be multiobservers support in the C++ core. [self processMyPositionStateModeEvent:location_helpers::mwmMyPositionMode(mode)]; }); + + // TODO(@igrechuhin): Move it to appropriate place. + BookmarkManager::AsyncLoadingCallbacks bookmarkCallbacks; + //bookmarkCallbacks.m_onStarted = ; + //bookmarkCallbacks.m_onFinished = ; + bookmarkCallbacks.m_onFileSuccess = [](std::string const & filePath, bool isTemporaryFile) + { + //[NSNotificationCenter.defaultCenter postNotificationName:@"KML file added" object:nil]; + //[self showLoadFileAlertIsSuccessful:YES]; + //[Statistics logEvent:kStatEventName(kStatApplication, kStatImport) + // withParameters:@{kStatValue : kStatKML}]; + }; + bookmarkCallbacks.m_onFileError = [](std::string const & filePath, bool isTemporaryFile) + { + //[self showLoadFileAlertIsSuccessful:NO]; + }; self.userTouchesAction = UserTouchesActionNone; + GetFramework().GetBookmarkManager().SetAsyncLoadingCallbacks(std::move(bookmarkCallbacks)); GetFramework().LoadBookmarks(); [MWMFrameworkListener addObserver:self]; } diff --git a/iphone/Maps/Classes/MapsAppDelegate.mm b/iphone/Maps/Classes/MapsAppDelegate.mm index 93a136768a..f47e57c211 100644 --- a/iphone/Maps/Classes/MapsAppDelegate.mm +++ b/iphone/Maps/Classes/MapsAppDelegate.mm @@ -298,13 +298,7 @@ using namespace osm_auth_ios; } else if (m_fileURL) { - if (!f.AddBookmarksFile(m_fileURL.UTF8String)) - [self showLoadFileAlertIsSuccessful:NO]; - - [NSNotificationCenter.defaultCenter postNotificationName:@"KML file added" object:nil]; - [self showLoadFileAlertIsSuccessful:YES]; - [Statistics logEvent:kStatEventName(kStatApplication, kStatImport) - withParameters:@{kStatValue : kStatKML}]; + f.AddBookmarksFile(m_fileURL.UTF8String, false /* isTemporaryFile */); } else { @@ -545,6 +539,9 @@ using namespace osm_auth_ios; }]; [[Crashlytics sharedInstance] recordError:err]; #endif + + // Global cleanup + DeleteFramework(); } - (void)applicationDidEnterBackground:(UIApplication *)application @@ -706,8 +703,6 @@ using namespace osm_auth_ios; - (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; - // Global cleanup - DeleteFramework(); } - (BOOL)initStatistics:(UIApplication *)application diff --git a/iphone/Maps/Core/Framework/Framework.cpp b/iphone/Maps/Core/Framework/Framework.cpp index 7a0f745ec9..606a5a0786 100644 --- a/iphone/Maps/Core/Framework/Framework.cpp +++ b/iphone/Maps/Core/Framework/Framework.cpp @@ -1,9 +1,13 @@ #include "Framework.h" +#include "base/assert.hpp" + static Framework * g_framework = 0; +bool wasDeleted = false; Framework & GetFramework() { + CHECK(!wasDeleted, ()); if (g_framework == 0) g_framework = new Framework(); return *g_framework; @@ -11,6 +15,7 @@ Framework & GetFramework() void DeleteFramework() { + wasDeleted = true; delete g_framework; g_framework = nullptr; } diff --git a/map/bookmark.cpp b/map/bookmark.cpp index 534175fb66..86de538f8a 100644 --- a/map/bookmark.cpp +++ b/map/bookmark.cpp @@ -168,6 +168,11 @@ void BookmarkCategory::DeleteTrack(size_t index) m_tracks.erase(next(m_tracks.begin(), index)); } +void BookmarkCategory::AcceptTracks(std::vector> && tracks) +{ + std::move(tracks.begin(), tracks.end(), std::back_inserter(m_tracks)); +} + namespace { std::string const kPlacemark = "Placemark"; @@ -563,7 +568,7 @@ bool BookmarkCategory::LoadFromKML(ReaderPtr const & reader) KMLParser parser(*this); if (!ParseXML(src, parser, true)) { - LOG(LERROR, ("XML read error. Probably, incorrect file encoding.")); + LOG(LWARNING, ("XML read error. Probably, incorrect file encoding.")); return false; } return true; diff --git a/map/bookmark.hpp b/map/bookmark.hpp index b0490abf89..4d0c996cba 100644 --- a/map/bookmark.hpp +++ b/map/bookmark.hpp @@ -110,12 +110,7 @@ private: class BookmarkCategory : public UserMarkContainer { - typedef UserMarkContainer TBase; - std::vector> m_tracks; - - std::string m_name; - /// Stores file name from which category was loaded - std::string m_file; + using TBase = UserMarkContainer; public: explicit BookmarkCategory(std::string const & name); @@ -128,13 +123,13 @@ public: void ClearTracks(); - /// @name Tracks routine. - //@{ void AddTrack(std::unique_ptr && track); Track const * GetTrack(size_t index) const; inline size_t GetTracksCount() const { return m_tracks.size(); } void DeleteTrack(size_t index); - //@} + + std::vector> && MoveTracks() { return std::move(m_tracks); } + void AcceptTracks(std::vector> && tracks); void SetName(std::string const & name) { m_name = name; } std::string const & GetName() const { return m_name; } @@ -161,6 +156,13 @@ public: protected: UserMark * AllocateUserMark(m2::PointD const & ptOrg) override; + +private: + std::vector> m_tracks; + + std::string m_name; + /// Stores file name from which category was loaded + std::string m_file; }; struct BookmarkAndCategory diff --git a/map/bookmark_manager.cpp b/map/bookmark_manager.cpp index fa44dc2c1d..c6fa7fdd5e 100644 --- a/map/bookmark_manager.cpp +++ b/map/bookmark_manager.cpp @@ -13,7 +13,10 @@ #include "indexer/scales.hpp" +#include "coding/file_name_utils.hpp" #include "coding/file_writer.hpp" +#include "coding/internal/file_data.hpp" +#include "coding/zip_reader.hpp" #include "geometry/transformations.hpp" @@ -28,6 +31,7 @@ namespace { char const * BOOKMARK_CATEGORY = "LastBookmarkCategory"; char const * BOOKMARK_TYPE = "LastBookmarkType"; +char const * KMZ_EXTENSION = ".kmz"; using SearchUserMarkContainer = SpecifiedUserMarkContainer; using ApiUserMarkContainer = SpecifiedUserMarkContainer; @@ -35,10 +39,33 @@ using DebugUserMarkContainer = SpecifiedUserMarkContainer; using LocalAdsMarkContainer = SpecifiedUserMarkContainer; using StaticUserMarkContainer = SpecifiedUserMarkContainer; + +// It returns extension with a dot in lower case +std::string const GetFileExt(std::string const & filePath) +{ + std::string ext = my::GetFileExtension(filePath); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return ext; +} + +std::string const GetFileName(std::string const & filePath) +{ + std::string ret = filePath; + my::GetNameFromFullPath(ret); + return ret; +} + +std::string const GenerateValidAndUniqueFilePathForKML(std::string const & fileName) +{ + std::string filePath = BookmarkCategory::RemoveInvalidSymbols(fileName); + filePath = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath); + return filePath; +} } // namespace BookmarkManager::BookmarkManager(GetStringsBundleFn && getStringsBundleFn) : m_getStringsBundle(std::move(getStringsBundleFn)) + , m_needTeardown(false) { ASSERT(m_getStringsBundle != nullptr, ()); @@ -79,6 +106,16 @@ void BookmarkManager::UpdateViewport(ScreenBase const & screen) m_viewport = screen; } +void BookmarkManager::SetAsyncLoadingCallbacks(AsyncLoadingCallbacks && callbacks) +{ + m_asyncLoadingCallbacks = std::move(callbacks); +} + +void BookmarkManager::Teardown() +{ + m_needTeardown = true; +} + void BookmarkManager::SaveState() const { settings::Set(BOOKMARK_CATEGORY, m_lastCategoryUrl); @@ -100,27 +137,152 @@ void BookmarkManager::LoadBookmarks() { ClearCategories(); - std::string const dir = GetPlatform().SettingsDir(); + StartAsyncLoading(); + GetPlatform().RunOnFileThread([this]() + { + std::string const dir = GetPlatform().SettingsDir(); + Platform::FilesList files; + Platform::GetFilesByExt(dir, BOOKMARKS_FILE_EXTENSION, files); - Platform::FilesList files; - Platform::GetFilesByExt(dir, BOOKMARKS_FILE_EXTENSION, files); - for (auto const & file : files) - LoadBookmark(dir + file); + auto collection = std::make_shared(); + for (auto const & file : files) + { + auto cat = BookmarkCategory::CreateFromKMLFile(dir + file); + if (m_needTeardown) + return; + + if (cat != nullptr) + collection->emplace_back(std::move(cat)); + } + FinishAsyncLoading(std::move(collection)); + }); LoadState(); } -void BookmarkManager::LoadBookmark(std::string const & filePath) +void BookmarkManager::LoadBookmark(std::string const & filePath, bool isTemporaryFile) { - auto cat = BookmarkCategory::CreateFromKMLFile(filePath); - if (cat != nullptr) + StartAsyncLoading(); + GetPlatform().RunOnFileThread([this, filePath, isTemporaryFile]() { - df::DrapeEngineLockGuard lock(m_drapeEngine); - if (lock) - cat->SetDrapeEngine(lock.Get()); + auto collection = std::make_shared(); + auto const fileSavePath = GetKMLPath(filePath); + if (m_needTeardown) + return; - m_categories.emplace_back(std::move(cat)); + if (!fileSavePath) + { + NotifyAboutFile(false /* success */, filePath, isTemporaryFile); + } + else + { + auto cat = BookmarkCategory::CreateFromKMLFile(fileSavePath.get()); + if (m_needTeardown) + return; + + bool const categoryExists = (cat != nullptr); + if (categoryExists) + collection->emplace_back(std::move(cat)); + + NotifyAboutFile(categoryExists, filePath, isTemporaryFile); + } + FinishAsyncLoading(std::move(collection)); + }); +} + +void BookmarkManager::StartAsyncLoading() +{ + if (m_needTeardown) + return; + + GetPlatform().RunOnGuiThread([this]() + { + m_asyncLoadingCounter++; + if (m_asyncLoadingCallbacks.m_onStarted != nullptr) + m_asyncLoadingCallbacks.m_onStarted(); + }); +} + +void BookmarkManager::FinishAsyncLoading(std::shared_ptr && collection) +{ + if (m_needTeardown) + return; + + GetPlatform().RunOnGuiThread([this, collection]() + { + if (!collection->empty()) + MergeCategories(std::move(*collection)); + + m_asyncLoadingCounter--; + if (m_asyncLoadingCounter == 0 && m_asyncLoadingCallbacks.m_onFinished != nullptr) + m_asyncLoadingCallbacks.m_onFinished(); + }); +} + +void BookmarkManager::NotifyAboutFile(bool success, std::string const & filePath, + bool isTemporaryFile) +{ + if (m_needTeardown) + return; + + GetPlatform().RunOnGuiThread([this, success, filePath, isTemporaryFile]() + { + if (success) + { + if (m_asyncLoadingCallbacks.m_onFileSuccess != nullptr) + m_asyncLoadingCallbacks.m_onFileSuccess(filePath, isTemporaryFile); + } + else + { + if (m_asyncLoadingCallbacks.m_onFileError != nullptr) + m_asyncLoadingCallbacks.m_onFileError(filePath, isTemporaryFile); + } + }); +} + +boost::optional BookmarkManager::GetKMLPath(std::string const & filePath) +{ + std::string const fileExt = GetFileExt(filePath); + string fileSavePath; + if (fileExt == BOOKMARKS_FILE_EXTENSION) + { + fileSavePath = GenerateValidAndUniqueFilePathForKML(GetFileName(filePath)); + if (!my::CopyFileX(filePath, fileSavePath)) + return {}; } + else if (fileExt == KMZ_EXTENSION) + { + try + { + ZipFileReader::FileListT files; + ZipFileReader::FilesList(filePath, files); + string kmlFileName; + for (size_t i = 0; i < files.size(); ++i) + { + if (GetFileExt(files[i].first) == BOOKMARKS_FILE_EXTENSION) + { + kmlFileName = files[i].first; + break; + } + } + if (kmlFileName.empty()) + return {}; + + fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileName); + ZipFileReader::UnzipFile(filePath, kmlFileName, fileSavePath); + } + catch (RootException const & e) + { + LOG(LWARNING, ("Error unzipping file", filePath, e.Msg())); + return {}; + } + } + else + { + LOG(LWARNING, ("Unknown file type", filePath)); + return {}; + } + return fileSavePath; } void BookmarkManager::InitBookmarks() @@ -273,9 +435,7 @@ UserMark const * BookmarkManager::FindNearestUserMark(TTouchRectHolder const & h finder(FindUserMarksContainer(UserMark::Type::SEARCH)); finder(FindUserMarksContainer(UserMark::Type::API)); for (auto & cat : m_categories) - { finder(cat.get()); - } return finder.GetFoundMark(); } @@ -336,6 +496,47 @@ std::unique_ptr const & BookmarkManager::MyPositionMark() c return m_myPositionMark; } +void BookmarkManager::MergeCategories(CategoriesCollection && newCategories) +{ + for (auto & category : m_categories) + { + // Since all KML-files are being loaded asynchronously user can create + // new category during loading. So we have to merge categories after loading. + std::string const categoryName = category->GetName(); + auto const it = std::find_if(newCategories.begin(), newCategories.end(), + [&categoryName](std::unique_ptr const & c) + { + return c->GetName() == categoryName; + }); + if (it != newCategories.end()) + { + // Copy bookmarks and tracks to the existing category. + for (size_t i = 0; i < (*it)->GetUserMarkCount(); ++i) + { + auto srcBookmark = static_cast((*it)->GetUserMark(i)); + auto bookmark = static_cast(category->CreateUserMark(srcBookmark->GetPivot())); + bookmark->SetData(srcBookmark->GetData()); + } + category->AcceptTracks((*it)->MoveTracks()); + category->SaveToKMLFile(); + + newCategories.erase(it); + } + } + + std::move(newCategories.begin(), newCategories.end(), std::back_inserter(m_categories)); + + df::DrapeEngineLockGuard lock(m_drapeEngine); + if (lock) + { + for (auto & cat : m_categories) + { + cat->SetDrapeEngine(lock.Get()); + cat->NotifyChanges(); + } + } +} + UserMarkNotificationGuard::UserMarkNotificationGuard(BookmarkManager & mng, UserMark::Type type) : m_controller(mng.GetUserMarksController(type)) {} diff --git a/map/bookmark_manager.hpp b/map/bookmark_manager.hpp index 95c5134198..e21ea9ee64 100644 --- a/map/bookmark_manager.hpp +++ b/map/bookmark_manager.hpp @@ -11,11 +11,14 @@ #include "base/macros.hpp" #include "base/strings_bundle.hpp" +#include #include #include #include #include +#include + class BookmarkManager final { using CategoriesCollection = std::vector>; @@ -25,17 +28,31 @@ class BookmarkManager final using GetStringsBundleFn = std::function; public: + using AsyncLoadingStartedCallback = std::function; + using AsyncLoadingFinishedCallback = std::function; + using AsyncLoadingFileCallback = std::function; + + struct AsyncLoadingCallbacks + { + AsyncLoadingStartedCallback m_onStarted; + AsyncLoadingFinishedCallback m_onFinished; + AsyncLoadingFileCallback m_onFileError; + AsyncLoadingFileCallback m_onFileSuccess; + }; + explicit BookmarkManager(GetStringsBundleFn && getStringsBundleFn); ~BookmarkManager(); void SetDrapeEngine(ref_ptr engine); void UpdateViewport(ScreenBase const & screen); + void SetAsyncLoadingCallbacks(AsyncLoadingCallbacks && callbacks); + void Teardown(); void ClearCategories(); /// Scans and loads all kml files with bookmarks in WritableDir. void LoadBookmarks(); - void LoadBookmark(std::string const & filePath); + void LoadBookmark(std::string const & filePath, bool isTemporaryFile); void InitBookmarks(); @@ -74,15 +91,24 @@ public: std::unique_ptr & MyPositionMark(); std::unique_ptr const & MyPositionMark() const; + bool IsAsyncLoadingInProgress() const { return m_asyncLoadingCounter != 0; } + private: UserMarkContainer const * FindUserMarksContainer(UserMark::Type type) const; UserMarkContainer * FindUserMarksContainer(UserMark::Type type); void SaveState() const; void LoadState(); + void MergeCategories(CategoriesCollection && newCategories); + void StartAsyncLoading(); + void FinishAsyncLoading(std::shared_ptr && collection); + boost::optional GetKMLPath(std::string const & filePath); + void NotifyAboutFile(bool success, std::string const & filePath, bool isTemporaryFile); GetStringsBundleFn m_getStringsBundle; df::DrapeEngineSafePtr m_drapeEngine; + AsyncLoadingCallbacks m_asyncLoadingCallbacks; + std::atomic m_needTeardown; ScreenBase m_viewport; @@ -94,6 +120,8 @@ private: std::unique_ptr m_selectionMark; std::unique_ptr m_myPositionMark; + size_t m_asyncLoadingCounter = 0; + DISALLOW_COPY_AND_MOVE(BookmarkManager); }; diff --git a/map/framework.cpp b/map/framework.cpp index 8672d654fb..4d9af61108 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -71,7 +71,6 @@ #include "platform/settings.hpp" #include "platform/socket.hpp" -#include "coding/internal/file_data.hpp" #include "coding/file_name_utils.hpp" #include "coding/transliteration.hpp" #include "coding/url_encode.hpp" @@ -105,8 +104,6 @@ #include "3party/Alohalytics/src/alohalytics.h" -#define KMZ_EXTENSION ".kmz" - #define DEFAULT_BOOKMARK_TYPE "placemark-red" using namespace storage; @@ -481,10 +478,13 @@ Framework::Framework(FrameworkParams const & params) Framework::~Framework() { + m_bmManager.Teardown(); m_trafficManager.Teardown(); m_localAdsManager.Teardown(); DestroyDrapeEngine(); m_model.SetOnMapDeregisteredCallback(nullptr); + + GetPlatform().ShutdownThreads(); } booking::Api * Framework::GetBookingApi(platform::NetworkPolicy const & policy) @@ -1008,80 +1008,9 @@ void Framework::ClearBookmarks() m_bmManager.ClearCategories(); } -namespace +void Framework::AddBookmarksFile(string const & filePath, bool isTemporaryFile) { - -/// @return extension with a dot in lower case -string const GetFileExt(string const & filePath) -{ - string ext = my::GetFileExtension(filePath); - transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - return ext; -} - -string const GetFileName(string const & filePath) -{ - string ret = filePath; - my::GetNameFromFullPath(ret); - return ret; -} - -string const GenerateValidAndUniqueFilePathForKML(string const & fileName) -{ - string filePath = BookmarkCategory::RemoveInvalidSymbols(fileName); - filePath = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath); - return filePath; -} - -} // namespace - -bool Framework::AddBookmarksFile(string const & filePath) -{ - string const fileExt = GetFileExt(filePath); - string fileSavePath; - if (fileExt == BOOKMARKS_FILE_EXTENSION) - { - fileSavePath = GenerateValidAndUniqueFilePathForKML(GetFileName(filePath)); - if (!my::CopyFileX(filePath, fileSavePath)) - return false; - } - else if (fileExt == KMZ_EXTENSION) - { - try - { - ZipFileReader::FileListT files; - ZipFileReader::FilesList(filePath, files); - string kmlFileName; - for (size_t i = 0; i < files.size(); ++i) - { - if (GetFileExt(files[i].first) == BOOKMARKS_FILE_EXTENSION) - { - kmlFileName = files[i].first; - break; - } - } - if (kmlFileName.empty()) - return false; - - fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileName); - ZipFileReader::UnzipFile(filePath, kmlFileName, fileSavePath); - } - catch (RootException const & e) - { - LOG(LWARNING, ("Error unzipping file", filePath, e.Msg())); - return false; - } - } - else - { - LOG(LWARNING, ("Unknown file type", filePath)); - return false; - } - - // Update freshly added bookmarks - m_bmManager.LoadBookmark(fileSavePath); - - return true; + m_bmManager.LoadBookmark(filePath, isTemporaryFile); } void Framework::PrepareToShutdown() diff --git a/map/framework.hpp b/map/framework.hpp index 8d10a5028e..1d71987194 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -331,7 +331,7 @@ public: void ClearBookmarks(); - bool AddBookmarksFile(string const & filePath); + void AddBookmarksFile(string const & filePath, bool isTemporaryFile); BookmarkAndCategory FindBookmark(UserMark const * mark) const; BookmarkManager & GetBookmarkManager() { return m_bmManager; } diff --git a/platform/platform.cpp b/platform/platform.cpp index 33a572340f..4be81636d8 100644 --- a/platform/platform.cpp +++ b/platform/platform.cpp @@ -269,6 +269,12 @@ unsigned Platform::CpuCores() const return cores > 0 ? cores : 1; } +void Platform::ShutdownThreads() +{ + m_networkThread.ShutdownAndJoin(); + m_fileThread.ShutdownAndJoin(); +} + string DebugPrint(Platform::EError err) { switch (err) diff --git a/platform/platform.hpp b/platform/platform.hpp index efdc6f8e31..28afc9958b 100644 --- a/platform/platform.hpp +++ b/platform/platform.hpp @@ -103,6 +103,7 @@ protected: unique_ptr m_guiThread; base::WorkerThread m_networkThread; + base::WorkerThread m_fileThread; public: Platform(); @@ -215,6 +216,9 @@ public: template void RunOnNetworkThread(Task && task) { m_networkThread.Push(forward(task)); } + template + void RunOnFileThread(Task && task) { m_fileThread.Push(forward(task)); } + enum Priority { EPriorityBackground, @@ -266,6 +270,8 @@ public: MarketingService & GetMarketingService() { return m_marketingService; } platform::SecureStorage & GetSecureStorage() { return m_secureStorage; } + void ShutdownThreads(); + // Use this method for testing purposes only. void SetGuiThread(unique_ptr guiThread);