#include "map/bookmark_manager.hpp" #include "map/api_mark_point.hpp" #include "map/local_ads_mark.hpp" #include "map/routing_mark.hpp" #include "map/search_mark.hpp" #include "map/user_mark.hpp" #include "drape_frontend/drape_engine.hpp" #include "drape_frontend/visual_params.hpp" #include "platform/platform.hpp" #include "platform/settings.hpp" #include "indexer/scales.hpp" #include "kml/serdes.hpp" #include "kml/serdes_binary.hpp" #include "coding/file_name_utils.hpp" #include "coding/file_writer.hpp" #include "coding/hex.hpp" #include "coding/internal/file_data.hpp" #include "coding/multilang_utf8_string.hpp" #include "coding/zip_creator.hpp" #include "coding/zip_reader.hpp" #include "geometry/transformations.hpp" #include "base/macros.hpp" #include "base/stl_add.hpp" #include "std/target_os.hpp" #include "3party/Alohalytics/src/alohalytics.h" #include #include #include #include #include using namespace std::placeholders; namespace { char const * BOOKMARK_CATEGORY = "LastBookmarkCategory"; char const * BOOKMARK_TYPE = "LastBookmarkType"; char const * KMZ_EXTENSION = ".kmz"; char const * kBookmarksExt = ".kmb"; // Returns extension with a dot in a lower case. std::string GetFileExt(std::string const & filePath) { std::string ext = my::GetFileExtension(filePath); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); return ext; } std::string GetFileName(std::string const & filePath) { std::string ret = filePath; my::GetNameFromFullPath(ret); return ret; } std::string GetBookmarksDirectory() { return my::JoinPath(GetPlatform().SettingsDir(), "bookmarks"); } bool IsBadCharForPath(strings::UniChar const & c) { static strings::UniChar const illegalChars[] = {':', '/', '\\', '<', '>', '\"', '|', '?', '*'}; for (size_t i = 0; i < ARRAY_SIZE(illegalChars); ++i) { if (c < ' ' || illegalChars[i] == c) return true; } return false; } class FindMarkFunctor { public: FindMarkFunctor(UserMark const ** mark, double & minD, m2::AnyRectD const & rect) : m_mark(mark) , m_minD(minD) , m_rect(rect) { m_globalCenter = rect.GlobalCenter(); } void operator()(UserMark const * mark) { m2::PointD const & org = mark->GetPivot(); if (m_rect.IsPointInside(org)) { double minDCandidate = m_globalCenter.SquareLength(org); if (minDCandidate < m_minD) { *m_mark = mark; m_minD = minDCandidate; } } } UserMark const ** m_mark; double & m_minD; m2::AnyRectD const & m_rect; m2::PointD m_globalCenter; }; BookmarkManager::SharingResult GetFileForSharing(df::MarkGroupID categoryId, std::string const & filePath) { if (!GetPlatform().IsFileExistsByFullPath(filePath)) { return BookmarkManager::SharingResult(categoryId, BookmarkManager::SharingResult::Code::FileError, "Bookmarks file does not exist."); } auto ext = my::GetFileExtension(filePath); strings::AsciiToLower(ext); std::string fileName = filePath; my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); auto const tmpFilePath = my::JoinFoldersToPath(GetPlatform().TmpDir(), fileName + KMZ_EXTENSION); if (ext == KMZ_EXTENSION) { if (my::CopyFileX(filePath, tmpFilePath)) return BookmarkManager::SharingResult(categoryId, tmpFilePath); return BookmarkManager::SharingResult(categoryId, BookmarkManager::SharingResult::Code::FileError, "Could not copy file."); } if (!CreateZipFromPathDeflatedAndDefaultCompression(filePath, tmpFilePath)) { return BookmarkManager::SharingResult(categoryId, BookmarkManager::SharingResult::Code::ArchiveError, "Could not create archive."); } return BookmarkManager::SharingResult(categoryId, tmpFilePath); } bool ConvertBeforeUploading(std::string const & filePath, std::string const & convertedFilePath) { //TODO: convert from kmb to kmz. return CreateZipFromPathDeflatedAndDefaultCompression(filePath, convertedFilePath); } bool ConvertAfterDownloading(std::string const & filePath, std::string const & convertedFilePath) { ZipFileReader::FileListT files; ZipFileReader::FilesList(filePath, files); if (files.empty()) return false; std::string const unarchievedPath = filePath + ".raw"; MY_SCOPE_GUARD(fileGuard, bind(&FileWriter::DeleteFileX, unarchievedPath)); ZipFileReader::UnzipFile(filePath, files.front().first, unarchievedPath); if (!GetPlatform().IsFileExistsByFullPath(unarchievedPath)) return false; kml::FileData kmlData; try { kml::DeserializerKml des(kmlData); FileReader reader(unarchievedPath); des.Deserialize(reader); } catch (FileReader::Exception const & exc) { LOG(LWARNING, ("KML text deserialization failure: ", exc.what(), "file:", unarchievedPath)); return false; } try { kml::binary::SerializerKml ser(kmlData); FileWriter writer(convertedFilePath); ser.Serialize(writer); } catch (FileWriter::Exception const & exc) { LOG(LWARNING, ("KML binary serialization failure: ", exc.what(), "file:", convertedFilePath)); return false; } return true; } } // namespace namespace migration { std::string const kSettingsParam = "BookmarksMigrationCompleted"; std::string GetBackupFolderName() { return my::JoinPath(GetPlatform().SettingsDir(), "bookmarks_backup"); } std::string CheckAndCreateBackupFolder() { auto const commonBackupFolder = GetBackupFolderName(); using Clock = std::chrono::system_clock; auto const ts = Clock::to_time_t(Clock::now()); tm * t = gmtime(&ts); std::ostringstream ss; ss << std::setfill('0') << std::setw(4) << t->tm_year + 1900 << std::setw(2) << t->tm_mon + 1 << std::setw(2) << t->tm_mday; auto const backupFolder = my::JoinPath(commonBackupFolder, ss.str()); // In the case if the folder exists, try to resume. if (GetPlatform().IsFileExistsByFullPath(backupFolder)) return backupFolder; // The backup will be in new folder. GetPlatform().RmDirRecursively(commonBackupFolder); if (!GetPlatform().MkDirChecked(commonBackupFolder)) return {}; if (!GetPlatform().MkDirChecked(backupFolder)) return {}; return backupFolder; } bool BackupBookmarks(std::string const & backupDir, std::vector const & files) { for (auto const & f : files) { std::string fileName = f; my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); auto const kmzPath = my::JoinPath(backupDir, fileName + KMZ_EXTENSION); if (GetPlatform().IsFileExistsByFullPath(kmzPath)) continue; if (!CreateZipFromPathDeflatedAndDefaultCompression(f, kmzPath)) return false; } return true; } bool ConvertBookmarks(std::vector const & files, size_t & convertedCount) { convertedCount = 0; auto const conversionFolder = my::JoinPath(GetBackupFolderName(), "conversion"); if (!GetPlatform().IsFileExistsByFullPath(conversionFolder) && !GetPlatform().MkDirChecked(conversionFolder)) { return false; } // Convert all files to kmb. std::vector convertedFiles; convertedFiles.reserve(files.size()); for (auto const & f : files) { std::string fileName = f; my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); auto const kmbPath = my::JoinPath(conversionFolder, fileName + kBookmarksExt); if (!GetPlatform().IsFileExistsByFullPath(kmbPath)) { kml::FileData kmlData; try { kml::DeserializerKml des(kmlData); FileReader reader(f); des.Deserialize(reader); } catch (FileReader::Exception const &exc) { LOG(LDEBUG, ("KML text deserialization failure: ", exc.what(), "file", f)); continue; } try { kml::binary::SerializerKml ser(kmlData); FileWriter writer(kmbPath); ser.Serialize(writer); } catch (FileWriter::Exception const &exc) { my::DeleteFileX(kmbPath); LOG(LDEBUG, ("KML binary serialization failure: ", exc.what(), "file", f)); continue; } } convertedFiles.push_back(kmbPath); } convertedCount = convertedFiles.size(); auto const newBookmarksDir = GetBookmarksDirectory(); if (!GetPlatform().IsFileExistsByFullPath(newBookmarksDir) && !GetPlatform().MkDirChecked(newBookmarksDir)) { return false; } // Move converted bookmark-files with respect of existing files. for (auto const & f : convertedFiles) { std::string fileName = f; my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); auto kmbPath = my::JoinPath(newBookmarksDir, fileName + kBookmarksExt); size_t counter = 1; while (Platform::IsFileExistsByFullPath(kmbPath)) { kmbPath = my::JoinPath(newBookmarksDir, fileName + strings::to_string(counter++) + kBookmarksExt); } if (!my::RenameFileX(f, kmbPath)) return false; } GetPlatform().RmDirRecursively(conversionFolder); return true; } void OnMigrationSuccess(size_t originalCount, size_t convertedCount) { settings::Set(kSettingsParam, true /* is completed */); alohalytics::TStringMap details{ {"original_count", strings::to_string(originalCount)}, {"converted_count", strings::to_string(convertedCount)}}; alohalytics::Stats::Instance().LogEvent("Bookmarks_migration_success", details); } void OnMigrationFailure(std::string && failedStage) { alohalytics::TStringMap details{ {"stage", std::move(failedStage)}, {"free_space", strings::to_string(GetPlatform().GetWritableStorageSpace())}}; alohalytics::Stats::Instance().LogEvent("Bookmarks_migration_failure", details); } bool IsMigrationCompleted() { bool isCompleted; if (!settings::Get(kSettingsParam, isCompleted)) return false; return isCompleted; } bool MigrateIfNeeded() { if (IsMigrationCompleted()) return true; std::string const dir = GetPlatform().SettingsDir(); Platform::FilesList files; Platform::GetFilesByExt(dir, BOOKMARKS_FILE_EXTENSION, files); if (files.empty()) { auto const newBookmarksDir = GetBookmarksDirectory(); if (!GetPlatform().IsFileExistsByFullPath(newBookmarksDir)) UNUSED_VALUE(GetPlatform().MkDirChecked(newBookmarksDir)); OnMigrationSuccess(0 /* originalCount */, 0 /* convertedCount */); return true; } for (auto & f : files) f = my::JoinFoldersToPath(dir, f); std::string failedStage; auto const backupDir = CheckAndCreateBackupFolder(); if (backupDir.empty() || !BackupBookmarks(backupDir, files)) { OnMigrationFailure("backup"); return false; } size_t convertedCount; if (!ConvertBookmarks(files, convertedCount)) { OnMigrationFailure("conversion"); return false; } //TODO(@darina): Uncomment after KMB integration. //for (auto const & f : files) // my::DeleteFileX(f); OnMigrationSuccess(files.size(), convertedCount); return true; } } // namespace migration using namespace std::placeholders; BookmarkManager::BookmarkManager(Callbacks && callbacks) : m_callbacks(std::move(callbacks)) , m_changesTracker(*this) , m_needTeardown(false) , m_nextGroupID(UserMark::BOOKMARK) , m_bookmarkCloud(Cloud::CloudParams("bmc.json", "bookmarks", "BookmarkCloudParam", GetBookmarksDirectory(), std::string(kBookmarksExt), std::bind(&ConvertBeforeUploading, _1, _2), std::bind(&ConvertAfterDownloading, _1, _2))) { ASSERT(m_callbacks.m_getStringsBundle != nullptr, ()); m_userMarkLayers.reserve(UserMark::BOOKMARK); for (uint32_t i = 0; i < UserMark::BOOKMARK; ++i) m_userMarkLayers.emplace_back(std::make_unique(static_cast(i))); m_selectionMark = CreateUserMark(m2::PointD{}); m_myPositionMark = CreateUserMark(m2::PointD{}); using namespace std::placeholders; m_bookmarkCloud.SetSynchronizationHandlers( std::bind(&BookmarkManager::OnSynchronizationStarted, this, _1), std::bind(&BookmarkManager::OnSynchronizationFinished, this, _1, _2, _3), std::bind(&BookmarkManager::OnRestoreRequested, this, _1, _2), std::bind(&BookmarkManager::OnRestoredFilesPrepared, this)); } BookmarkManager::EditSession BookmarkManager::GetEditSession() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return EditSession(*this); } UserMark const * BookmarkManager::GetMark(df::MarkID markId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (IsBookmark(markId)) return GetBookmark(markId); return GetUserMark(markId); } UserMark const * BookmarkManager::GetUserMark(df::MarkID markId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_userMarks.find(markId); return (it != m_userMarks.end()) ? it->second.get() : nullptr; } UserMark * BookmarkManager::GetUserMarkForEdit(df::MarkID markId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_userMarks.find(markId); if (it != m_userMarks.end()) { m_changesTracker.OnUpdateMark(markId); return it->second.get(); } return nullptr; } void BookmarkManager::DeleteUserMark(df::MarkID markId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ASSERT(!IsBookmark(markId), ()); auto it = m_userMarks.find(markId); auto const groupId = it->second->GetGroupId(); GetGroup(groupId)->DetachUserMark(markId); m_changesTracker.OnDeleteMark(markId); m_userMarks.erase(it); } Bookmark * BookmarkManager::CreateBookmark(kml::BookmarkData const & bmData) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return AddBookmark(std::make_unique(bmData)); } Bookmark * BookmarkManager::CreateBookmark(kml::BookmarkData & bm, df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetPlatform().GetMarketingService().SendMarketingEvent(marketing::kBookmarksBookmarkAction, {{"action", "create"}}); bm.m_timestamp = std::chrono::system_clock::now(); bm.m_viewportScale = static_cast(df::GetZoomLevel(m_viewport.GetScale())); auto * bookmark = CreateBookmark(bm); bookmark->Attach(groupId); auto * group = GetBmCategory(groupId); group->AttachUserMark(bookmark->GetId()); group->SetIsVisible(true); m_lastCategoryUrl = group->GetFileName(); m_lastType = bookmark->GetIcon(); SaveState(); return bookmark; } Bookmark const * BookmarkManager::GetBookmark(df::MarkID markId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_bookmarks.find(markId); return (it != m_bookmarks.end()) ? it->second.get() : nullptr; } Bookmark * BookmarkManager::GetBookmarkForEdit(df::MarkID markId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_bookmarks.find(markId); if (it == m_bookmarks.end()) return nullptr; auto const groupId = it->second->GetGroupId(); if (groupId != df::kInvalidMarkGroupId) m_changesTracker.OnUpdateMark(markId); return it->second.get(); } void BookmarkManager::AttachBookmark(df::MarkID bmId, df::MarkGroupID catID) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetBookmarkForEdit(bmId)->Attach(catID); GetGroup(catID)->AttachUserMark(bmId); } void BookmarkManager::DetachBookmark(df::MarkID bmId, df::MarkGroupID catID) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetBookmarkForEdit(bmId)->Detach(); GetGroup(catID)->DetachUserMark(bmId); } void BookmarkManager::DeleteBookmark(df::MarkID bmId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ASSERT(IsBookmark(bmId), ()); auto groupIt = m_bookmarks.find(bmId); auto const groupId = groupIt->second->GetGroupId(); if (groupId) GetGroup(groupId)->DetachUserMark(bmId); m_changesTracker.OnDeleteMark(bmId); m_bookmarks.erase(groupIt); } Track * BookmarkManager::CreateTrack(kml::TrackData const & trackData) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return AddTrack(std::make_unique(trackData)); } Track const * BookmarkManager::GetTrack(df::LineID trackId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_tracks.find(trackId); return (it != m_tracks.end()) ? it->second.get() : nullptr; } void BookmarkManager::AttachTrack(df::LineID trackId, df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_tracks.find(trackId); it->second->Attach(groupId); GetBmCategory(groupId)->AttachTrack(trackId); } void BookmarkManager::DetachTrack(df::LineID trackId, df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetBmCategory(groupId)->DetachTrack(trackId); } void BookmarkManager::DeleteTrack(df::LineID trackId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_tracks.find(trackId); auto const groupId = it->second->GetGroupId(); if (groupId != df::kInvalidMarkGroupId) GetBmCategory(groupId)->DetachTrack(trackId); m_changesTracker.OnDeleteLine(trackId); m_tracks.erase(it); } void BookmarkManager::CollectDirtyGroups(df::GroupIDSet & dirtyGroups) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto const & group : m_userMarkLayers) { if (!group->IsDirty()) continue; auto const groupId = static_cast(group->GetType()); dirtyGroups.insert(groupId); } for (auto const & group : m_categories) { if (!group.second->IsDirty()) continue; dirtyGroups.insert(group.first); } } void BookmarkManager::OnEditSessionOpened() { ++m_openedEditSessionsCount; } void BookmarkManager::OnEditSessionClosed() { ASSERT_GREATER(m_openedEditSessionsCount, 0, ()); if (--m_openedEditSessionsCount == 0) NotifyChanges(); } void BookmarkManager::NotifyChanges() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (!m_changesTracker.CheckChanges() && !m_firstDrapeNotification) return; bool isBookmarks = false; for (auto groupId : m_changesTracker.GetDirtyGroupIds()) { if (IsBookmarkCategory(groupId)) { if (GetBmCategory(groupId)->IsAutoSaveEnabled()) SaveBookmarkCategoryToFile(groupId); isBookmarks = true; } } if (isBookmarks) SendBookmarksChanges(); df::DrapeEngineLockGuard lock(m_drapeEngine); if (lock) { auto engine = lock.Get(); for (auto groupId : m_changesTracker.GetDirtyGroupIds()) { auto * group = GetGroup(groupId); engine->ChangeVisibilityUserMarksGroup(groupId, group->IsVisible()); } for (auto groupId : m_changesTracker.GetRemovedGroupIds()) engine->ClearUserMarksGroup(groupId); engine->UpdateUserMarks(&m_changesTracker, m_firstDrapeNotification); m_firstDrapeNotification = false; for (auto groupId : m_changesTracker.GetDirtyGroupIds()) { auto * group = GetGroup(groupId); group->ResetChanges(); } engine->InvalidateUserMarks(); } for (auto const markId : m_changesTracker.GetUpdatedMarkIds()) GetMark(markId)->ResetChanges(); m_changesTracker.ResetChanges(); } df::MarkIDSet const & BookmarkManager::GetUserMarkIds(df::MarkGroupID groupId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetGroup(groupId)->GetUserMarks(); } df::LineIDSet const & BookmarkManager::GetTrackIds(df::MarkGroupID groupId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetGroup(groupId)->GetUserLines(); } void BookmarkManager::ClearGroup(df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto * group = GetGroup(groupId); for (auto markId : group->GetUserMarks()) { m_changesTracker.OnDeleteMark(markId); if (IsBookmarkCategory(groupId)) m_bookmarks.erase(markId); else m_userMarks.erase(markId); } for (auto trackId : group->GetUserLines()) { m_changesTracker.OnDeleteLine(trackId); m_tracks.erase(trackId); } group->Clear(); } std::string BookmarkManager::GetCategoryName(df::MarkGroupID categoryId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetBmCategory(categoryId)->GetName(); } void BookmarkManager::SetCategoryName(df::MarkGroupID categoryId, std::string const & name) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetBmCategory(categoryId)->SetName(name); } std::string BookmarkManager::GetCategoryFileName(df::MarkGroupID categoryId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetBmCategory(categoryId)->GetFileName(); } UserMark const * BookmarkManager::FindMarkInRect(df::MarkGroupID groupId, m2::AnyRectD const & rect, double & d) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto const * group = GetGroup(groupId); UserMark const * resMark = nullptr; if (group->IsVisible()) { FindMarkFunctor f(&resMark, d, rect); for (auto markId : group->GetUserMarks()) { auto const * mark = GetMark(markId); if (mark->IsAvailableForSearch() && rect.IsPointInside(mark->GetPivot())) f(mark); } } return resMark; } void BookmarkManager::SetIsVisible(df::MarkGroupID groupId, bool visible) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); GetGroup(groupId)->SetIsVisible(visible); } bool BookmarkManager::IsVisible(df::MarkGroupID groupId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetGroup(groupId)->IsVisible(); } void BookmarkManager::SetDrapeEngine(ref_ptr engine) { m_drapeEngine.Set(engine); m_firstDrapeNotification = true; } 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; } Bookmark * BookmarkManager::AddBookmark(std::unique_ptr && bookmark) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto * bm = bookmark.get(); auto const markId = bm->GetId(); ASSERT(m_bookmarks.count(markId) == 0, ()); m_bookmarks.emplace(markId, std::move(bookmark)); m_changesTracker.OnAddMark(markId); return bm; } Track * BookmarkManager::AddTrack(std::unique_ptr && track) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto * t = track.get(); auto const trackId = t->GetId(); ASSERT(m_tracks.count(trackId) == 0, ()); m_tracks.emplace(trackId, std::move(track)); m_changesTracker.OnAddLine(trackId); return t; } void BookmarkManager::SaveState() const { settings::Set(BOOKMARK_CATEGORY, m_lastCategoryUrl); settings::Set(BOOKMARK_TYPE, m_lastType); } void BookmarkManager::LoadState() { UNUSED_VALUE(settings::Get(BOOKMARK_CATEGORY, m_lastCategoryUrl)); UNUSED_VALUE(settings::Get(BOOKMARK_TYPE, m_lastType)); } void BookmarkManager::ClearCategories() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto groupId : m_bmGroupsIdList) { ClearGroup(groupId); m_changesTracker.OnDeleteGroup(groupId); } m_categories.clear(); m_bmGroupsIdList.clear(); m_bookmarks.clear(); m_tracks.clear(); } std::shared_ptr BookmarkManager::LoadBookmarksKML(std::vector & filePaths) { std::string const dir = GetPlatform().SettingsDir(); Platform::FilesList files; Platform::GetFilesByExt(dir, BOOKMARKS_FILE_EXTENSION, files); auto collection = std::make_shared(); collection->reserve(files.size()); filePaths.reserve(files.size()); for (auto const & file : files) { auto const filePath = dir + file; auto kmlData = std::make_unique(); try { kml::DeserializerKml des(*kmlData); FileReader reader(filePath); des.Deserialize(reader); } catch (FileReader::Exception const &exc) { LOG(LDEBUG, ("KML deserialization failure: ", exc.what(), "file", filePath)); continue; } if (m_needTeardown) break; filePaths.push_back(filePath); collection->emplace_back(filePath, std::move(kmlData)); /*auto kmlData = LoadKMLFile(filePath); if (m_needTeardown) break; if (kmlData) { filePaths.push_back(filePath); collection->emplace_back(filePath, std::move(kmlData)); }*/ } return collection; } std::shared_ptr BookmarkManager::LoadBookmarksKMB(std::vector & filePaths) { std::string const dir = GetPlatform().SettingsDir(); Platform::FilesList files; Platform::GetFilesByExt(dir, kBookmarksExt, files); auto collection = std::make_shared(); collection->reserve(files.size()); filePaths.reserve(files.size()); for (auto const & file : files) { auto const filePath = dir + file; auto kmlData = std::make_unique(); try { kml::binary::DeserializerKml des(*kmlData); FileReader reader(filePath); des.Deserialize(reader); } catch (FileReader::Exception const &exc) { LOG(LDEBUG, ("KML binary deserialization failure: ", exc.what(), "file", filePath)); continue; } if (m_needTeardown) break; filePaths.push_back(filePath); collection->emplace_back(filePath, std::move(kmlData)); } return collection; } void BookmarkManager::LoadBookmarks() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ClearCategories(); m_loadBookmarksFinished = false; NotifyAboutStartAsyncLoading(); GetPlatform().RunTask(Platform::Thread::File, [this]() { bool const migrated = migration::MigrateIfNeeded(); std::vector filePaths; auto collection = migrated ? LoadBookmarksKMB(filePaths) : LoadBookmarksKML(filePaths); if (m_needTeardown) return; NotifyAboutFinishAsyncLoading(std::move(collection)); GetPlatform().RunTask(Platform::Thread::Gui, [this, filePaths]() { m_bookmarkCloud.Init(filePaths); }); }); LoadState(); } void BookmarkManager::MigrateAndLoadBookmarks() { } void BookmarkManager::LoadBookmark(std::string const & filePath, bool isTemporaryFile) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (!m_loadBookmarksFinished || m_asyncLoadingInProgress) { m_bookmarkLoadingQueue.emplace_back(filePath, isTemporaryFile); return; } LoadBookmarkRoutine(filePath, isTemporaryFile); } void BookmarkManager::LoadBookmarkRoutine(std::string const & filePath, bool isTemporaryFile) { ASSERT(!m_asyncLoadingInProgress, ()); NotifyAboutStartAsyncLoading(); GetPlatform().RunTask(Platform::Thread::File, [this, filePath, isTemporaryFile]() { bool const migrated = migration::IsMigrationCompleted(); auto collection = std::make_shared(); auto kmlData = std::make_unique(); std::string fileSavePath; try { auto const savePath = GetKMLPath(filePath); if (m_needTeardown) return; if (savePath) { kml::DeserializerKml des(*kmlData); FileReader reader(fileSavePath); des.Deserialize(reader); fileSavePath = savePath.get(); } } catch (FileReader::Exception const & exc) { LOG(LDEBUG, ("KML deserialization failure: ", exc.what(), "file", filePath)); } if (m_needTeardown) return; if (migrated) { std::string fileName = fileSavePath; my::DeleteFileX(fileSavePath); my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); fileSavePath = GenerateValidAndUniqueFilePathForKMB(fileName); try { kml::binary::SerializerKml ser(*kmlData); FileWriter writer(fileSavePath); ser.Serialize(writer); } catch (FileWriter::Exception const & exc) { my::DeleteFileX(fileSavePath); LOG(LDEBUG, ("KML binary serialization failure: ", exc.what(), "file", fileSavePath)); fileSavePath.clear(); } } if (m_needTeardown) return; if (!fileSavePath.empty()) collection->emplace_back(fileSavePath, std::move(kmlData)); NotifyAboutFile(!fileSavePath.empty() /* success */, filePath, isTemporaryFile); NotifyAboutFinishAsyncLoading(std::move(collection)); if (!fileSavePath.empty()) { // TODO(darina): should we use the cloud only for KMB? GetPlatform().RunTask(Platform::Thread::Gui, [this, fileSavePath]() { m_bookmarkCloud.Init({fileSavePath}); }); } }); } void BookmarkManager::NotifyAboutStartAsyncLoading() { if (m_needTeardown) return; GetPlatform().RunTask(Platform::Thread::Gui, [this]() { m_asyncLoadingInProgress = true; if (m_asyncLoadingCallbacks.m_onStarted != nullptr) m_asyncLoadingCallbacks.m_onStarted(); }); } void BookmarkManager::NotifyAboutFinishAsyncLoading(std::shared_ptr && collection) { if (m_needTeardown) return; GetPlatform().RunTask(Platform::Thread::Gui, [this, collection]() { m_asyncLoadingInProgress = false; m_loadBookmarksFinished = true; if (!collection->empty()) CreateCategories(std::move(*collection)); else CheckAndCreateDefaultCategory(); if (m_asyncLoadingCallbacks.m_onFinished != nullptr) m_asyncLoadingCallbacks.m_onFinished(); if (!m_bookmarkLoadingQueue.empty()) { LoadBookmarkRoutine(m_bookmarkLoadingQueue.front().m_filename, m_bookmarkLoadingQueue.front().m_isTemporaryFile); m_bookmarkLoadingQueue.pop_front(); } }); } void BookmarkManager::NotifyAboutFile(bool success, std::string const & filePath, bool isTemporaryFile) { if (m_needTeardown) return; GetPlatform().RunTask(Platform::Thread::Gui, [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); std::string kmlFileName; std::string ext; for (size_t i = 0; i < files.size(); ++i) { ext = GetFileExt(files[i].first); if (ext == 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::MoveBookmark(df::MarkID bmID, df::MarkGroupID curGroupID, df::MarkGroupID newGroupID) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); DetachBookmark(bmID, curGroupID); AttachBookmark(bmID, newGroupID); } void BookmarkManager::UpdateBookmark(df::MarkID bmID, kml::BookmarkData const & bm) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto * bookmark = GetBookmarkForEdit(bmID); bookmark->SetData(bm); ASSERT(bookmark->GetGroupId() != df::kInvalidMarkGroupId, ()); m_lastType = bookmark->GetIcon(); SaveState(); } df::MarkGroupID BookmarkManager::LastEditedBMCategory() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto & cat : m_categories) { if (cat.second->GetFileName() == m_lastCategoryUrl) return cat.first; } CheckAndCreateDefaultCategory(); return m_bmGroupsIdList.front(); } std::string BookmarkManager::LastEditedBMType() const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return (m_lastType.empty() ? BookmarkCategory::GetDefaultType() : m_lastType); } BookmarkCategory const * BookmarkManager::GetBmCategory(df::MarkGroupID categoryId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ASSERT(IsBookmarkCategory(categoryId), ()); auto const it = m_categories.find(categoryId); return (it != m_categories.end() ? it->second.get() : nullptr); } BookmarkCategory * BookmarkManager::GetBmCategory(df::MarkGroupID categoryId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ASSERT(IsBookmarkCategory(categoryId), ()); auto const it = m_categories.find(categoryId); return (it != m_categories.end() ? it->second.get() : nullptr); } void BookmarkManager::SendBookmarksChanges() { if (m_callbacks.m_createdBookmarksCallback != nullptr) { std::vector> marksInfo; GetBookmarksData(m_changesTracker.GetCreatedMarkIds(), marksInfo); m_callbacks.m_createdBookmarksCallback(marksInfo); } if (m_callbacks.m_updatedBookmarksCallback != nullptr) { std::vector> marksInfo; GetBookmarksData(m_changesTracker.GetUpdatedMarkIds(), marksInfo); m_callbacks.m_updatedBookmarksCallback(marksInfo); } if (m_callbacks.m_deletedBookmarksCallback != nullptr) { df::MarkIDCollection idCollection; auto const & removedIds = m_changesTracker.GetRemovedMarkIds(); idCollection.reserve(removedIds.size()); for (auto markId : removedIds) { if (IsBookmark(markId)) idCollection.push_back(markId); } m_callbacks.m_deletedBookmarksCallback(idCollection); } } void BookmarkManager::GetBookmarksData(df::MarkIDSet const & markIds, std::vector> & data) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); data.clear(); data.reserve(markIds.size()); for (auto markId : markIds) { auto const * bookmark = GetBookmark(markId); if (bookmark) data.emplace_back(markId, bookmark->GetData()); } } bool BookmarkManager::HasBmCategory(df::MarkGroupID groupId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return m_categories.find(groupId) != m_categories.end(); } df::MarkGroupID BookmarkManager::CreateBookmarkCategory(kml::CategoryData const & data, bool autoSave) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto const groupId = m_nextGroupID++; auto & cat = m_categories[groupId]; cat = my::make_unique(data, groupId, autoSave); m_bmGroupsIdList.push_back(groupId); m_changesTracker.OnAddGroup(groupId); return groupId; } df::MarkGroupID BookmarkManager::CreateBookmarkCategory(std::string const & name, bool autoSave) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto const groupId = m_nextGroupID++; auto & cat = m_categories[groupId]; cat = my::make_unique(name, groupId, autoSave); m_bmGroupsIdList.push_back(groupId); m_changesTracker.OnAddGroup(groupId); return groupId; } void BookmarkManager::CheckAndCreateDefaultCategory() { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (m_categories.empty()) CreateBookmarkCategory(m_callbacks.m_getStringsBundle().GetString("core_my_places")); } bool BookmarkManager::DeleteBmCategory(df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); auto it = m_categories.find(groupId); if (it == m_categories.end()) return false; ClearGroup(groupId); m_changesTracker.OnDeleteGroup(groupId); FileWriter::DeleteFileX(it->second->GetFileName()); m_categories.erase(it); m_bmGroupsIdList.erase(std::remove(m_bmGroupsIdList.begin(), m_bmGroupsIdList.end(), groupId), m_bmGroupsIdList.end()); return true; } namespace { class BestUserMarkFinder { public: explicit BestUserMarkFinder(BookmarkManager::TTouchRectHolder const & rectHolder, BookmarkManager const * manager) : m_rectHolder(rectHolder) , m_d(numeric_limits::max()) , m_mark(nullptr) , m_manager(manager) {} void operator()(df::MarkGroupID groupId) { m2::AnyRectD const & rect = m_rectHolder(min((UserMark::Type)groupId, UserMark::BOOKMARK)); if (UserMark const * p = m_manager->FindMarkInRect(groupId, rect, m_d)) { static double const kEps = 1e-5; if (m_mark == nullptr || !p->GetPivot().EqualDxDy(m_mark->GetPivot(), kEps)) m_mark = p; } } UserMark const * GetFoundMark() const { return m_mark; } private: BookmarkManager::TTouchRectHolder const & m_rectHolder; double m_d; UserMark const * m_mark; BookmarkManager const * m_manager; }; } // namespace UserMark const * BookmarkManager::FindNearestUserMark(m2::AnyRectD const & rect) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return FindNearestUserMark([&rect](UserMark::Type) { return rect; }); } UserMark const * BookmarkManager::FindNearestUserMark(TTouchRectHolder const & holder) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); BestUserMarkFinder finder(holder, this); finder(UserMark::Type::ROUTING); finder(UserMark::Type::SEARCH); finder(UserMark::Type::API); for (auto & pair : m_categories) finder(pair.first); return finder.GetFoundMark(); } UserMarkLayer const * BookmarkManager::GetGroup(df::MarkGroupID groupId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (groupId < UserMark::Type::BOOKMARK) return m_userMarkLayers[groupId].get(); ASSERT(m_categories.find(groupId) != m_categories.end(), ()); return m_categories.at(groupId).get(); } UserMarkLayer * BookmarkManager::GetGroup(df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); if (groupId < UserMark::Type::BOOKMARK) return m_userMarkLayers[groupId].get(); auto const it = m_categories.find(groupId); return it != m_categories.end() ? it->second.get() : nullptr; } void BookmarkManager::CreateCategories(KMLDataCollection && dataCollection, bool autoSave) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); df::GroupIDSet loadedGroups; std::vector> categoriesForMerge; categoriesForMerge.reserve(m_categories.size()); for (auto const & c : m_categories) categoriesForMerge.emplace_back(c.first, c.second.get()); for (auto const & data : dataCollection) { df::MarkGroupID groupId; BookmarkCategory * group = nullptr; auto const & fileName = data.first; auto const * fileData = data.second.get(); auto const & categoryData = fileData->m_categoryData; auto const it = std::find_if(categoriesForMerge.cbegin(), categoriesForMerge.cend(), [categoryData](auto const & v) { return v.second->GetName() == kml::GetDefaultStr(categoryData.m_name); }); bool const merge = it != categoriesForMerge.cend(); if (merge) { groupId = it->first; group = it->second; } else { groupId = CreateBookmarkCategory(categoryData, false /* autoSave */); loadedGroups.insert(groupId); group = GetBmCategory(groupId); group->SetFileName(fileName); } for (auto & bmData : fileData->m_bookmarksData) { auto * bm = CreateBookmark(bmData); bm->Attach(groupId); group->AttachUserMark(bm->GetId()); } for (auto & trackData : fileData->m_tracksData) { auto track = make_unique(trackData); auto * t = AddTrack(std::move(track)); t->Attach(groupId); group->AttachTrack(t->GetId()); } if (merge) { // Delete file since it will be merged. my::DeleteFileX(fileName); SaveBookmarkCategoryToFile(groupId); } } NotifyChanges(); for (auto const & groupId : loadedGroups) { auto * group = GetBmCategory(groupId); group->EnableAutoSave(autoSave); } } std::unique_ptr BookmarkManager::CollectBmGroupKMLData(BookmarkCategory const * group) const { auto kmlData = std::make_unique(); kmlData->m_categoryData = group->GetCategoryData(); auto const & markIds = group->GetUserMarks(); kmlData->m_bookmarksData.reserve(markIds.size()); for (auto it = markIds.rbegin(); it != markIds.rend(); ++it) { Bookmark const *bm = GetBookmark(*it); kmlData->m_bookmarksData.emplace_back(bm->GetData()); } auto const & lineIds = group->GetUserLines(); kmlData->m_tracksData.reserve(lineIds.size()); for (auto trackId : lineIds) { Track const *track = GetTrack(trackId); kmlData->m_tracksData.emplace_back(track->GetData()); } return kmlData; } void BookmarkManager::SaveToKML(df::MarkGroupID groupId, Writer & writer, bool useBinary) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); SaveToKML(GetBmCategory(groupId), writer, useBinary); } void BookmarkManager::SaveToKML(BookmarkCategory const * group, Writer & writer, bool useBinary) const { auto const kmlData = CollectBmGroupKMLData(group); if (useBinary) { kml::binary::SerializerKml ser(*kmlData); ser.Serialize(writer); } else { kml::SerializerKml ser(*kmlData); ser.Serialize(writer); } } bool BookmarkManager::SaveBookmarkCategoryToFile(df::MarkGroupID groupId) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); std::string oldFile; auto * group = GetBmCategory(groupId); // Get valid file name from category name std::string const name = RemoveInvalidSymbols(group->GetName()); std::string file = group->GetFileName(); bool migrated = migration::IsMigrationCompleted(); std::string const fileExt(migrated ? kBookmarksExt : BOOKMARKS_FILE_EXTENSION); std::string const fileDir = migrated ? GetBookmarksDirectory() : GetPlatform().SettingsDir(); if (migrated && !GetPlatform().IsFileExistsByFullPath(fileDir) && !GetPlatform().MkDirChecked(fileDir)) return false; if (file.empty()) { file = GenerateUniqueFileName(fileDir, name, fileExt); group->SetFileName(file); } std::string const fileTmp = file + ".tmp"; try { FileWriter writer(fileTmp); SaveToKML(group, writer, migrated /* useBinary */); // Only after successful save we replace original file my::DeleteFileX(file); VERIFY(my::RenameFileX(fileTmp, file), (fileTmp, file)); return true; } catch (FileWriter::Exception const &exc) { LOG(LDEBUG, ("KML", migrated ? " binary" : "", " serialization failure: ", exc.what(), "file", fileTmp)); } catch (std::exception const & e) { LOG(LWARNING, ("Exception while saving bookmarks:", e.what())); } LOG(LWARNING, ("Can't save bookmarks category", name, "to file", file)); // remove possibly left tmp file my::DeleteFileX(fileTmp); return false; } void BookmarkManager::SetCloudEnabled(bool enabled) { m_bookmarkCloud.SetState(enabled ? Cloud::State::Enabled : Cloud::State::Disabled); } bool BookmarkManager::IsCloudEnabled() const { return m_bookmarkCloud.GetState() == Cloud::State::Enabled; } uint64_t BookmarkManager::GetLastSynchronizationTimestampInMs() const { return m_bookmarkCloud.GetLastSynchronizationTimestampInMs(); } std::unique_ptr BookmarkManager::GetUserSubscriber() { return m_bookmarkCloud.GetUserSubscriber(); } void BookmarkManager::SetInvalidTokenHandler(Cloud::InvalidTokenHandler && onInvalidToken) { m_bookmarkCloud.SetInvalidTokenHandler(std::move(onInvalidToken)); } void BookmarkManager::PrepareFileForSharing(df::MarkGroupID categoryId, SharingHandler && handler) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); ASSERT(handler, ()); if (IsCategoryEmpty(categoryId)) { handler(SharingResult(categoryId, SharingResult::Code::EmptyCategory)); return; } auto const filePath = GetCategoryFileName(categoryId); GetPlatform().RunTask(Platform::Thread::File, [categoryId, filePath, handler = std::move(handler)]() { handler(GetFileForSharing(categoryId, filePath)); }); } bool BookmarkManager::IsCategoryEmpty(df::MarkGroupID categoryId) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); return GetBmCategory(categoryId)->IsEmpty(); } bool BookmarkManager::IsUsedCategoryName(std::string const & name) const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto const & c : m_categories) { if (c.second->GetName() == name) return true; } return false; } bool BookmarkManager::AreAllCategoriesVisible() const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto const & c : m_categories) { if (!c.second->IsVisible()) return false; } return true; } bool BookmarkManager::AreAllCategoriesInvisible() const { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto const & c : m_categories) { if (c.second->IsVisible()) return false; } return true; } void BookmarkManager::SetAllCategoriesVisibility(bool visible) { ASSERT_THREAD_CHECKER(m_threadChecker, ()); for (auto & c : m_categories) c.second->SetIsVisible(visible); } size_t BookmarkManager::GetKmlFilesCountForConversion() const { // The conversion available only after successful migration. if (!migration::IsMigrationCompleted()) return 0; Platform::FilesList files; Platform::GetFilesByExt(GetPlatform().SettingsDir(), BOOKMARKS_FILE_EXTENSION, files); return files.size(); } void BookmarkManager::ConvertAllKmlFiles(ConversionHandler && handler) const { // The conversion available only after successful migration. if (!migration::IsMigrationCompleted()) return; GetPlatform().RunTask(Platform::Thread::File, [handler = std::move(handler)]() { auto const oldDir = GetPlatform().SettingsDir(); Platform::FilesList files; Platform::GetFilesByExt(oldDir, BOOKMARKS_FILE_EXTENSION, files); for (auto & f : files) f = my::JoinFoldersToPath(oldDir, f); auto const newDir = GetBookmarksDirectory(); if (!GetPlatform().IsFileExistsByFullPath(newDir) && !GetPlatform().MkDirChecked(newDir)) { handler(false /* success */); return; } std::vector> fileData; fileData.reserve(files.size()); for (auto const & f : files) { std::string fileName = f; my::GetNameFromFullPath(fileName); my::GetNameWithoutExt(fileName); auto kmbPath = my::JoinPath(newDir, fileName + kBookmarksExt); size_t counter = 1; while (Platform::IsFileExistsByFullPath(kmbPath)) kmbPath = my::JoinPath(newDir, fileName + strings::to_string(counter++) + kBookmarksExt); kml::FileData kmlData; try { kml::DeserializerKml des(kmlData); FileReader reader(f); des.Deserialize(reader); } catch (FileReader::Exception const & exc) { LOG(LDEBUG, ("KML text deserialization failure: ", exc.what(), "file", f)); handler(false /* success */); return; } try { kml::binary::SerializerKml ser(kmlData); FileWriter writer(kmbPath); ser.Serialize(writer); } catch (FileWriter::Exception const & exc) { my::DeleteFileX(kmbPath); LOG(LDEBUG, ("KML binary serialization failure: ", exc.what(), "file", f)); handler(false /* success */); return; } fileData.emplace_back(kmbPath, std::move(kmlData)); } for (auto const & f : files) my::DeleteFileX(f); //TODO(@darina): add fileData to m_categories. handler(true /* success */); }); } void BookmarkManager::SetCloudHandlers( Cloud::SynchronizationStartedHandler && onSynchronizationStarted, Cloud::SynchronizationFinishedHandler && onSynchronizationFinished, Cloud::RestoreRequestedHandler && onRestoreRequested, Cloud::RestoredFilesPreparedHandler && onRestoredFilesPrepared) { m_onSynchronizationStarted = std::move(onSynchronizationStarted); m_onSynchronizationFinished = std::move(onSynchronizationFinished); m_onRestoreRequested = std::move(onRestoreRequested); m_onRestoredFilesPrepared = std::move(onRestoredFilesPrepared); } void BookmarkManager::OnSynchronizationStarted(Cloud::SynchronizationType type) { GetPlatform().RunTask(Platform::Thread::Gui, [this, type]() { if (m_onSynchronizationStarted) m_onSynchronizationStarted(type); }); LOG(LINFO, ("Cloud Synchronization Started:", type)); } void BookmarkManager::OnSynchronizationFinished(Cloud::SynchronizationType type, Cloud::SynchronizationResult result, std::string const & errorStr) { GetPlatform().RunTask(Platform::Thread::Gui, [this, type, result, errorStr]() { if (m_onSynchronizationFinished) m_onSynchronizationFinished(type, result, errorStr); if (type == Cloud::SynchronizationType::Restore && result == Cloud::SynchronizationResult::Success) { // Reload bookmarks after restoring. LoadBookmarks(); } }); LOG(LINFO, ("Cloud Synchronization Finished:", type, result, errorStr)); } void BookmarkManager::OnRestoreRequested(Cloud::RestoringRequestResult result, uint64_t backupTimestampInMs) { GetPlatform().RunTask(Platform::Thread::Gui, [this, result, backupTimestampInMs]() { if (m_onRestoreRequested) m_onRestoreRequested(result, backupTimestampInMs); }); using namespace std::chrono; LOG(LINFO, ("Cloud Restore Requested:", result, time_point(milliseconds(backupTimestampInMs)))); } void BookmarkManager::OnRestoredFilesPrepared() { // This method is always called from UI-thread. ClearCategories(); if (m_onRestoredFilesPrepared) m_onRestoredFilesPrepared(); LOG(LINFO, ("Cloud Restore Files: Prepared")); } void BookmarkManager::RequestCloudRestoring() { m_bookmarkCloud.RequestRestoring(); } void BookmarkManager::ApplyCloudRestoring() { m_bookmarkCloud.ApplyRestoring(); } void BookmarkManager::CancelCloudRestoring() { m_bookmarkCloud.CancelRestoring(); } df::GroupIDSet BookmarkManager::MarksChangesTracker::GetAllGroupIds() const { auto const & groupIds = m_bmManager.GetBmGroupsIdList(); df::GroupIDSet resultingSet(groupIds.begin(), groupIds.end()); for (uint32_t i = 0; i < UserMark::BOOKMARK; ++i) resultingSet.insert(static_cast(i)); return resultingSet; } bool BookmarkManager::MarksChangesTracker::IsGroupVisible(df::MarkGroupID groupId) const { return m_bmManager.IsVisible(groupId); } bool BookmarkManager::MarksChangesTracker::IsGroupVisibilityChanged(df::MarkGroupID groupId) const { return m_bmManager.GetGroup(groupId)->IsVisibilityChanged(); } df::MarkIDSet const & BookmarkManager::MarksChangesTracker::GetGroupPointIds(df::MarkGroupID groupId) const { return m_bmManager.GetUserMarkIds(groupId); } df::LineIDSet const & BookmarkManager::MarksChangesTracker::GetGroupLineIds(df::MarkGroupID groupId) const { return m_bmManager.GetTrackIds(groupId); } df::UserPointMark const * BookmarkManager::MarksChangesTracker::GetUserPointMark(df::MarkID markId) const { return m_bmManager.GetMark(markId); } df::UserLineMark const * BookmarkManager::MarksChangesTracker::GetUserLineMark(df::LineID lineId) const { return m_bmManager.GetTrack(lineId); } void BookmarkManager::MarksChangesTracker::OnAddMark(df::MarkID markId) { m_createdMarks.insert(markId); } void BookmarkManager::MarksChangesTracker::OnDeleteMark(df::MarkID markId) { auto const it = m_createdMarks.find(markId); if (it != m_createdMarks.end()) { m_createdMarks.erase(it); } else { m_updatedMarks.erase(markId); m_removedMarks.insert(markId); } } void BookmarkManager::MarksChangesTracker::OnUpdateMark(df::MarkID markId) { if (m_createdMarks.find(markId) == m_createdMarks.end()) m_updatedMarks.insert(markId); } void BookmarkManager::MarksChangesTracker::OnAddLine(df::LineID lineId) { m_createdLines.insert(lineId); } void BookmarkManager::MarksChangesTracker::OnDeleteLine(df::LineID lineId) { auto const it = m_createdLines.find(lineId); if (it != m_createdLines.end()) m_createdLines.erase(it); else m_removedLines.insert(lineId); } void BookmarkManager::MarksChangesTracker::OnAddGroup(df::MarkGroupID groupId) { m_createdGroups.insert(groupId); } void BookmarkManager::MarksChangesTracker::OnDeleteGroup(df::MarkGroupID groupId) { auto const it = m_createdGroups.find(groupId); if (it != m_createdGroups.end()) m_createdGroups.erase(it); else m_removedGroups.insert(groupId); } bool BookmarkManager::MarksChangesTracker::CheckChanges() { m_bmManager.CollectDirtyGroups(m_dirtyGroups); for (auto const markId : m_updatedMarks) { auto const * mark = m_bmManager.GetMark(markId); if (mark->IsDirty()) m_dirtyGroups.insert(mark->GetGroupId()); } return !m_dirtyGroups.empty() || !m_removedGroups.empty(); } void BookmarkManager::MarksChangesTracker::ResetChanges() { m_dirtyGroups.clear(); m_createdGroups.clear(); m_removedGroups.clear(); m_createdMarks.clear(); m_removedMarks.clear(); m_updatedMarks.clear(); m_createdLines.clear(); m_removedLines.clear(); } // static std::string BookmarkManager::RemoveInvalidSymbols(std::string const & name) { // Remove not allowed symbols strings::UniString uniName = strings::MakeUniString(name); uniName.erase_if(&IsBadCharForPath); return (uniName.empty() ? "Bookmarks" : strings::ToUtf8(uniName)); } // static std::string BookmarkManager::GenerateUniqueFileName(const std::string & path, std::string name, std::string const & kmlExt) { // check if file name already contains .kml extension size_t const extPos = name.rfind(kmlExt); if (extPos != std::string::npos) { // remove extension ASSERT_GREATER_OR_EQUAL(name.size(), kmlExt.size(), ()); size_t const expectedPos = name.size() - kmlExt.size(); if (extPos == expectedPos) name.resize(expectedPos); } size_t counter = 1; std::string suffix; while (Platform::IsFileExistsByFullPath(my::JoinPath(path, name + suffix + kmlExt))) suffix = strings::to_string(counter++); return my::JoinPath(path, name + suffix + kmlExt); } // static std::string BookmarkManager::GenerateValidAndUniqueFilePathForKML(std::string const & fileName) { std::string filePath = RemoveInvalidSymbols(fileName); return GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath, BOOKMARKS_FILE_EXTENSION); } // static std::string BookmarkManager::GenerateValidAndUniqueFilePathForKMB(std::string const & fileName) { std::string filePath = RemoveInvalidSymbols(fileName); return GenerateUniqueFileName(GetBookmarksDirectory(), filePath, kBookmarksExt); } // static bool BookmarkManager::IsMigrated() { return migration::IsMigrationCompleted(); } BookmarkManager::EditSession::EditSession(BookmarkManager & manager) : m_bmManager(manager) { m_bmManager.OnEditSessionOpened(); } BookmarkManager::EditSession::~EditSession() { m_bmManager.OnEditSessionClosed(); } Bookmark * BookmarkManager::EditSession::CreateBookmark(kml::BookmarkData const & bm) { return m_bmManager.CreateBookmark(bm); } Bookmark * BookmarkManager::EditSession::CreateBookmark(kml::BookmarkData & bm, df::MarkGroupID groupId) { return m_bmManager.CreateBookmark(bm, groupId); } Track * BookmarkManager::EditSession::CreateTrack(kml::TrackData const & trackData) { return m_bmManager.CreateTrack(trackData); } Bookmark * BookmarkManager::EditSession::GetBookmarkForEdit(df::MarkID markId) { return m_bmManager.GetBookmarkForEdit(markId); } void BookmarkManager::EditSession::DeleteUserMark(df::MarkID markId) { m_bmManager.DeleteUserMark(markId); } void BookmarkManager::EditSession::DeleteBookmark(df::MarkID bmId) { m_bmManager.DeleteBookmark(bmId); } void BookmarkManager::EditSession::DeleteTrack(df::LineID trackId) { m_bmManager.DeleteTrack(trackId); } void BookmarkManager::EditSession::ClearGroup(df::MarkGroupID groupId) { m_bmManager.ClearGroup(groupId); } void BookmarkManager::EditSession::SetIsVisible(df::MarkGroupID groupId, bool visible) { m_bmManager.SetIsVisible(groupId, visible); } void BookmarkManager::EditSession::MoveBookmark( df::MarkID bmID, df::MarkGroupID curGroupID, df::MarkGroupID newGroupID) { m_bmManager.MoveBookmark(bmID, curGroupID, newGroupID); } void BookmarkManager::EditSession::UpdateBookmark(df::MarkID bmId, kml::BookmarkData const & bm) { return m_bmManager.UpdateBookmark(bmId, bm); } void BookmarkManager::EditSession::AttachBookmark(df::MarkID bmId, df::MarkGroupID groupId) { m_bmManager.AttachBookmark(bmId, groupId); } void BookmarkManager::EditSession::DetachBookmark(df::MarkID bmId, df::MarkGroupID groupId) { m_bmManager.DetachBookmark(bmId, groupId); } void BookmarkManager::EditSession::AttachTrack(df::LineID trackId, df::MarkGroupID groupId) { m_bmManager.AttachTrack(trackId, groupId); } void BookmarkManager::EditSession::DetachTrack(df::LineID trackId, df::MarkGroupID groupId) { m_bmManager.DetachTrack(trackId, groupId); } void BookmarkManager::EditSession::SetCategoryName(df::MarkGroupID categoryId, std::string const & name) { m_bmManager.SetCategoryName(categoryId, name); } bool BookmarkManager::EditSession::DeleteBmCategory(df::MarkGroupID groupId) { return m_bmManager.DeleteBmCategory(groupId); } void BookmarkManager::EditSession::NotifyChanges() { m_bmManager.NotifyChanges(); }