[bookmarks] [ios] Mark deleted categories with .deleted postfix instead of file deletion #8719

Closed
kirylkaveryn wants to merge 2 commits from recently-deleted-categories-using-postfix into master
5 changed files with 160 additions and 15 deletions

View file

@ -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,

View file

@ -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.

View file

@ -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()

View file

@ -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);

View file

@ -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