[bookmarks] [ios] Mark deleted categories with .deleted
postfix instead of file deletion #8719
5 changed files with 160 additions and 15 deletions
|
@ -252,17 +252,19 @@ std::string GenerateUniqueFileName(std::string const & path, std::string name, s
|
|||
name.resize(name.size() - ext.size());
|
||||
|
||||
size_t counter = 1;
|
||||
std::string suffix, res;
|
||||
std::string suffix, resultFilePath, deletedFilePath;
|
||||
do
|
||||
{
|
||||
res = name;
|
||||
res = base::JoinPath(path, res.append(suffix).append(ext));
|
||||
if (!Platform::IsFileExistsByFullPath(res))
|
||||
resultFilePath = name;
|
||||
deletedFilePath = name;
|
||||
resultFilePath = base::JoinPath(path, resultFilePath.append(suffix).append(ext));
|
||||
deletedFilePath = base::JoinPath(path, deletedFilePath.append(suffix).append(ext).append(kDeletedExtension));
|
||||
if (!Platform::IsFileExistsByFullPath(resultFilePath) && !Platform::IsFileExistsByFullPath(deletedFilePath))
|
||||
break;
|
||||
suffix = strings::to_string(counter++);
|
||||
} while (true);
|
||||
|
||||
return res;
|
||||
return resultFilePath;
|
||||
}
|
||||
|
||||
std::string GenerateValidAndUniqueFilePathForKML(std::string const & fileName)
|
||||
|
@ -283,6 +285,15 @@ std::string GenerateValidAndUniqueFilePathForGPX(std::string const & fileName)
|
|||
return GenerateUniqueFileName(GetBookmarksDirectory(), std::move(filePath), kGpxExtension);
|
||||
}
|
||||
|
||||
std::string GenerateValidAndUniqueDeletedFilePath(std::string const & fileName)
|
||||
{
|
||||
std::string filePath = RemoveInvalidSymbols(fileName);
|
||||
if (filePath.empty())
|
||||
filePath = kDefaultBookmarksFileName;
|
||||
|
||||
return GenerateUniqueFileName(GetBookmarksDirectory(), std::move(filePath), kDeletedExtension);
|
||||
}
|
||||
|
||||
std::string const kDefaultBookmarksFileName = "Bookmarks";
|
||||
|
||||
// Populate empty category & track names based on file name: assign file name to category name,
|
||||
|
|
|
@ -69,6 +69,7 @@ std::string_view constexpr kKmzExtension = ".kmz";
|
|||
std::string_view constexpr kKmlExtension = ".kml";
|
||||
std::string_view constexpr kKmbExtension = ".kmb";
|
||||
std::string_view constexpr kGpxExtension = ".gpx";
|
||||
std::string_view constexpr kDeletedExtension = ".deleted";
|
||||
extern std::string const kDefaultBookmarksFileName;
|
||||
|
||||
enum class KmlFileType
|
||||
|
@ -96,6 +97,7 @@ std::string RemoveInvalidSymbols(std::string const & name);
|
|||
std::string GenerateUniqueFileName(const std::string & path, std::string name, std::string_view ext = kKmlExtension);
|
||||
std::string GenerateValidAndUniqueFilePathForKML(std::string const & fileName);
|
||||
std::string GenerateValidAndUniqueFilePathForGPX(std::string const & fileName);
|
||||
std::string GenerateValidAndUniqueDeletedFilePath(std::string const & fileName);
|
||||
/// @}
|
||||
|
||||
/// @name SerDes helpers.
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
|
@ -404,6 +408,59 @@ void BookmarkManager::ResetRecentlyDeletedBookmark()
|
|||
m_recentlyDeletedBookmark.reset();
|
||||
}
|
||||
|
||||
bool BookmarkManager::HasRecentlyDeletedCategories() const
|
||||
{
|
||||
Platform::FilesList files;
|
||||
Platform::GetFilesByExt(GetBookmarksDirectory(), kDeletedExtension, files);
|
||||
return !files.empty();
|
||||
}
|
||||
|
||||
BookmarkManager::KMLDataCollectionPtr BookmarkManager::GetRecentlyDeletedCategories()
|
||||
{
|
||||
auto collection = LoadBookmarks(GetBookmarksDirectory(), kDeletedExtension, KmlFileType::Text,
|
||||
[](kml::FileData const &)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
|
||||
bool BookmarkManager::IsRecentlyDeletedCategory(const std::string &filePath) const
|
||||
{
|
||||
return base::GetFileExtension(filePath) == kDeletedExtension;
|
||||
}
|
||||
|
||||
void BookmarkManager::RecoverRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & deletedFilePaths)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
GetPlatform().RunTask(Platform::Thread::File, [this, deletedFilePaths]()
|
||||
{
|
||||
for (auto const & deletedFilePath : deletedFilePaths)
|
||||
{
|
||||
CHECK(IsRecentlyDeletedCategory(deletedFilePath), ("The category at path", deletedFilePath, " should be 'deleted'."));
|
||||
ASSERT(Platform::IsFileExistsByFullPath(deletedFilePath), ("Deleted file should exist to be recovered.", deletedFilePath));
|
||||
auto filePath = base::FilenameWithoutExt(deletedFilePath);
|
||||
if (Platform::IsFileExistsByFullPath(filePath))
|
||||
filePath = GenerateValidAndUniqueFilePathForKML(base::GetNameFromFullPathWithoutExt(deletedFilePath));
|
||||
UpdateLastModifiedTime(deletedFilePath);
|
||||
base::MoveFileX(deletedFilePath, filePath);
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, filePath]() {
|
||||
ReloadBookmark(filePath);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BookmarkManager::DeleteRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & deletedFilePaths)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
for (auto const & deletedFilePath : deletedFilePaths)
|
||||
{
|
||||
CHECK(IsRecentlyDeletedCategory(deletedFilePath), ("The category at path", deletedFilePath, " should be 'deleted'."));
|
||||
FileWriter::DeleteFileX(deletedFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
void BookmarkManager::DetachUserMark(kml::MarkId bmId, kml::MarkGroupId catId)
|
||||
{
|
||||
GetGroup(catId)->DetachUserMark(bmId);
|
||||
|
@ -2056,10 +2113,10 @@ void BookmarkManager::LoadBookmarkRoutine(std::string const & filePath, bool isT
|
|||
kmlData = LoadKmlFile(fileToLoad, KmlFileType::Text);
|
||||
else if (ext == kGpxExtension)
|
||||
kmlData = LoadKmlFile(fileToLoad, KmlFileType::Gpx);
|
||||
else if (ext == kDeletedExtension)
|
||||
kmlData = nullptr;
|
||||
else
|
||||
{
|
||||
ASSERT(false, ("Unsupported bookmarks extension", ext));
|
||||
}
|
||||
|
||||
base::DeleteFileX(fileToLoad);
|
||||
|
||||
|
@ -2102,6 +2159,8 @@ void BookmarkManager::ReloadBookmarkRoutine(std::string const & filePath)
|
|||
kmlData = LoadKmlFile(filePath, KmlFileType::Text);
|
||||
else if (ext == kGpxExtension)
|
||||
kmlData = LoadKmlFile(filePath, KmlFileType::Gpx);
|
||||
else if (ext == kDeletedExtension)
|
||||
kmlData = nullptr;
|
||||
else
|
||||
ASSERT(false, ("Unsupported bookmarks extension", ext));
|
||||
|
||||
|
@ -2485,7 +2544,7 @@ void BookmarkManager::CheckAndResetLastIds()
|
|||
idStorage.ResetTrackId();
|
||||
}
|
||||
|
||||
bool BookmarkManager::DeleteBmCategory(kml::MarkGroupId groupId, bool deleteFile)
|
||||
bool BookmarkManager::DeleteBmCategory(kml::MarkGroupId groupId, bool permanently)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
auto it = m_categories.find(groupId);
|
||||
|
@ -2495,8 +2554,15 @@ bool BookmarkManager::DeleteBmCategory(kml::MarkGroupId groupId, bool deleteFile
|
|||
ClearGroup(groupId);
|
||||
m_changesTracker.OnDeleteGroup(groupId);
|
||||
|
||||
if (deleteFile)
|
||||
FileWriter::DeleteFileX(it->second->GetFileName());
|
||||
auto const filePath = it->second->GetFileName();
|
||||
if (permanently)
|
||||
FileWriter::DeleteFileX(filePath);
|
||||
else
|
||||
{
|
||||
UpdateLastModifiedTime(filePath);
|
||||
auto const deletedFilePath = GenerateValidAndUniqueDeletedFilePath(base::FileNameFromFullPath(filePath));
|
||||
base::MoveFileX(filePath, deletedFilePath);
|
||||
}
|
||||
|
||||
DeleteCompilations(it->second->GetCategoryData().m_compilationIds);
|
||||
m_categories.erase(it);
|
||||
|
@ -2504,6 +2570,18 @@ bool BookmarkManager::DeleteBmCategory(kml::MarkGroupId groupId, bool deleteFile
|
|||
return true;
|
||||
}
|
||||
|
||||
void BookmarkManager::UpdateLastModifiedTime(const std::string & filePath)
|
||||
{
|
||||
struct timeval tv[2];
|
||||
gettimeofday(&tv[0], NULL);
|
||||
tv[1] = tv[0];
|
||||
#ifdef _MSC_VER
|
||||
_utime(filePath.c_str(), nullptr);
|
||||
#else
|
||||
utimes(filePath.c_str(), nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class BestUserMarkFinder
|
||||
|
@ -3573,9 +3651,9 @@ void BookmarkManager::EditSession::SetCategoryCustomProperty(kml::MarkGroupId ca
|
|||
m_bmManager.SetCategoryCustomProperty(categoryId, key, value);
|
||||
}
|
||||
|
||||
bool BookmarkManager::EditSession::DeleteBmCategory(kml::MarkGroupId groupId)
|
||||
bool BookmarkManager::EditSession::DeleteBmCategory(kml::MarkGroupId groupId, bool permanently)
|
||||
{
|
||||
return m_bmManager.DeleteBmCategory(groupId, true);
|
||||
return m_bmManager.DeleteBmCategory(groupId, permanently);
|
||||
}
|
||||
|
||||
void BookmarkManager::EditSession::NotifyChanges()
|
||||
|
|
|
@ -161,8 +161,11 @@ public:
|
|||
void SetCategoryAccessRules(kml::MarkGroupId categoryId, kml::AccessRules accessRules);
|
||||
void SetCategoryCustomProperty(kml::MarkGroupId categoryId, std::string const & key,
|
||||
std::string const & value);
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId);
|
||||
|
||||
|
||||
/// Removes the category from the list of categories and deletes the related file.
|
||||
/// - Parameters:
|
||||
/// - permanently: If true (default value), the file will be removed from the disk. If false, the file will be set as deleted.
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId, bool permanently = true);
|
||||
void NotifyChanges();
|
||||
|
||||
private:
|
||||
|
@ -378,6 +381,13 @@ public:
|
|||
bool HasRecentlyDeletedBookmark() const { return m_recentlyDeletedBookmark.operator bool(); };
|
||||
void ResetRecentlyDeletedBookmark();
|
||||
|
||||
bool HasRecentlyDeletedCategories() const;
|
||||
BookmarkManager::KMLDataCollectionPtr GetRecentlyDeletedCategories();
|
||||
bool IsRecentlyDeletedCategory(std::string const & filePath) const;
|
||||
|
||||
void RecoverRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & filePaths);
|
||||
void DeleteRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & filePaths);
|
||||
|
||||
// Used for LoadBookmarks() and unit tests only. Does *not* update last modified time.
|
||||
void CreateCategories(KMLDataCollection && dataCollection, bool autoSave = false);
|
||||
|
||||
|
@ -581,8 +591,9 @@ private:
|
|||
void SetCategoryTags(kml::MarkGroupId categoryId, std::vector<std::string> const & tags);
|
||||
void SetCategoryAccessRules(kml::MarkGroupId categoryId, kml::AccessRules accessRules);
|
||||
void SetCategoryCustomProperty(kml::MarkGroupId categoryId, std::string const & key, std::string const & value);
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId, bool deleteFile);
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId, bool permanently);
|
||||
void ClearCategories();
|
||||
void UpdateLastModifiedTime(const std::string & filePath);
|
||||
|
||||
void MoveBookmark(kml::MarkId bmID, kml::MarkGroupId curGroupID, kml::MarkGroupId newGroupID);
|
||||
void UpdateBookmark(kml::MarkId bmId, kml::BookmarkData const & bm);
|
||||
|
|
|
@ -1530,4 +1530,47 @@ UNIT_CLASS_TEST(Runner, Bookmarks_BrokenFile)
|
|||
auto kmlData = LoadKmlFile(fileName, KmlFileType::Binary);
|
||||
TEST(kmlData == nullptr, ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(Runner, Bookmarks_RecentlyDeleted)
|
||||
{
|
||||
BookmarkManager bmManager(BM_CALLBACKS);
|
||||
bmManager.EnableTestMode(true);
|
||||
auto const dir = GetBookmarksDirectory();
|
||||
bool const delDirOnExit = Platform::MkDir(dir) == Platform::ERR_OK;
|
||||
SCOPE_GUARD(dirDeleter, [&](){ if (delDirOnExit) (void)Platform::RmDir(dir); });
|
||||
|
||||
std::string const filePath = base::JoinPath(dir, "file" + std::string{kKmlExtension});
|
||||
BookmarkManager::KMLDataCollection kmlDataCollection;
|
||||
kmlDataCollection.emplace_back(filePath, LoadKmlData(MemReader(kmlString, std::strlen(kmlString)), KmlFileType::Text));
|
||||
|
||||
FileWriter w(filePath);
|
||||
w.Write(kmlDataCollection.data(), kmlDataCollection.size());
|
||||
|
||||
TEST(kmlDataCollection.back().second, ());
|
||||
bmManager.CreateCategories(std::move(kmlDataCollection));
|
||||
TEST_EQUAL(bmManager.GetBmGroupsCount(), 1, ());
|
||||
|
||||
auto const groupId = bmManager.GetUnsortedBmGroupsIdList().front();
|
||||
|
||||
bmManager.GetEditSession().DeleteBmCategory(groupId, false /* permanently */);
|
||||
TEST_EQUAL(bmManager.GetBmGroupsCount(), 0, ());
|
||||
|
||||
auto const deletedCategories = bmManager.GetRecentlyDeletedCategories();
|
||||
TEST_EQUAL(deletedCategories->size(), 1, ());
|
||||
TEST(bmManager.HasRecentlyDeletedCategories(), ());
|
||||
|
||||
auto const & deletedCategory = deletedCategories->front();
|
||||
TEST_EQUAL(base::GetFileExtension(deletedCategory.first), std::string{kDeletedExtension}, ());
|
||||
|
||||
auto const deletedFilePath = filePath + std::string{kDeletedExtension};
|
||||
TEST_EQUAL(deletedCategory.first, deletedFilePath, ());
|
||||
|
||||
bmManager.DeleteRecentlyDeletedCategoriesAtPaths({ deletedFilePath });
|
||||
TEST_EQUAL(bmManager.GetBmGroupsCount(), 0, ());
|
||||
TEST_EQUAL(bmManager.GetRecentlyDeletedCategories()->size(), 0, ());
|
||||
TEST(!bmManager.HasRecentlyDeletedCategories(), ());
|
||||
|
||||
TEST(!Platform::IsFileExistsByFullPath(filePath), ());
|
||||
TEST(!Platform::IsFileExistsByFullPath(deletedFilePath), ());
|
||||
}
|
||||
} // namespace bookmarks_test
|
||||
|
|
Reference in a new issue