summaryrefslogtreecommitdiff
path: root/view/sharedcache/core/SharedCacheView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'view/sharedcache/core/SharedCacheView.cpp')
-rw-r--r--view/sharedcache/core/SharedCacheView.cpp208
1 files changed, 179 insertions, 29 deletions
diff --git a/view/sharedcache/core/SharedCacheView.cpp b/view/sharedcache/core/SharedCacheView.cpp
index 00436c5f..77fbd1d2 100644
--- a/view/sharedcache/core/SharedCacheView.cpp
+++ b/view/sharedcache/core/SharedCacheView.cpp
@@ -23,6 +23,25 @@ static bool IsUsablePrimaryCachePath(const std::string& path)
&& std::filesystem::is_regular_file(path, ec);
}
+
+static std::string PathRelativeTo(const std::string& path, const std::string& basePath)
+{
+ std::error_code ec;
+ auto relativePath = std::filesystem::relative(path, basePath, ec);
+ auto relativePathString = relativePath.generic_string();
+ if (ec || relativePath.empty() || relativePathString == ".." || relativePathString.find("../") == 0)
+ return path;
+ return relativePath.string();
+}
+
+
+static std::string ResolveRelativePath(const std::string& path, const std::string& basePath)
+{
+ if (path.empty() || std::filesystem::path(path).is_absolute())
+ return path;
+ return (std::filesystem::path(basePath) / path).string();
+}
+
SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {}
// We register all our one-shot stuff here, such as the object destructor.
@@ -852,8 +871,8 @@ bool SharedCacheView::InitController()
}
std::string primaryFileDir = std::filesystem::path(*primaryFilePath).parent_path().string();
- // Get the primary project file from the current files project.
- // This is required to allow selecting a primary file in a different directory. Otherwise, we search the current database directory.
+ // If the primary file is in the current project, use its project folder to discover related cache files.
+ // Otherwise, fall back to scanning the resolved primary file's directory on disk.
Ref<ProjectFile> primaryProjectFile = nullptr;
auto currentProjectFile = GetFile()->GetProjectFile();
if (currentProjectFile)
@@ -861,8 +880,7 @@ bool SharedCacheView::InitController()
if (!IsSameFolderForFile(primaryProjectFile, currentProjectFile))
{
- // TODO: Remove this restriction using stored cache UUID's and a fast project file search.
- m_logger->LogWarn("Because the primary file is in a different project folder you will need to select it on every open, consider moving the database file into the same folder.");
+ m_logger->LogWarn("The primary shared cache file is not in the same project folder as this database. Related cache files will be resolved from the primary file's project folder or directory.");
}
// OK, we have the primary shared cache file, now let's add the entries.
@@ -1014,6 +1032,15 @@ void SharedCacheView::OnAfterSnapshotDataApplied()
void SharedCacheView::SetPrimaryFileName(std::string primaryFileName)
{
+ m_primaryFilePath.clear();
+ m_primaryFileName = std::move(primaryFileName);
+ GetParentView()->StoreMetadata(VIEW_METADATA_KEY, GetMetadata());
+}
+
+
+void SharedCacheView::SetPrimaryFileLocation(std::string primaryFilePath, std::string primaryFileName)
+{
+ m_primaryFilePath = std::move(primaryFilePath);
m_primaryFileName = std::move(primaryFileName);
GetParentView()->StoreMetadata(VIEW_METADATA_KEY, GetMetadata());
}
@@ -1027,6 +1054,104 @@ void SharedCacheView::LogSecondaryFileName(std::string secondaryFileName)
std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
{
auto viewFile = GetFile();
+ auto databaseDir = std::filesystem::path(viewFile->GetFilename()).parent_path().string();
+ auto currentProjectFile = viewFile->GetProjectFile();
+ Ref<Project> project = nullptr;
+ if (currentProjectFile)
+ project = currentProjectFile->GetProject();
+
+ auto storePrimaryProjectFile = [&](ProjectFile* projectFile) -> std::string {
+ SetPrimaryFileLocation(projectFile->GetPathInProject(), projectFile->GetName());
+ return projectFile->GetPathOnDisk();
+ };
+
+ auto storePrimaryFilePath = [&](const std::string& path) {
+ if (project)
+ {
+ if (auto projectFile = project->GetFileByPathOnDisk(path))
+ {
+ SetPrimaryFileLocation(projectFile->GetPathInProject(), projectFile->GetName());
+ return;
+ }
+
+ m_logger->LogWarnF(
+ "Primary shared cache file '{}' is outside the current project. Add the shared cache files to the project to avoid selecting them on future opens.",
+ path);
+ SetPrimaryFileName(BaseFileName(path));
+ return;
+ }
+
+ SetPrimaryFileLocation(PathRelativeTo(path, databaseDir), BaseFileName(path));
+ };
+
+ auto resolveProjectFilePath = [&](const std::string& projectPath) -> std::optional<std::string> {
+ if (!project || projectPath.empty())
+ return std::nullopt;
+
+ try
+ {
+ auto matches = project->GetFilesByPathInProject(projectPath);
+ if (matches.size() > 1)
+ {
+ m_logger->LogErrorF(
+ "Multiple project files match primary shared cache path '{}'. Provide loader.dsc.primaryFilePath with an unambiguous project path.",
+ projectPath);
+ return std::string();
+ }
+ if (matches.size() == 1)
+ return storePrimaryProjectFile(matches[0]);
+ }
+ catch (const std::exception& e)
+ {
+ m_logger->LogWarnForExceptionF(e, "Failed to resolve primary shared cache project path '{}': {}", projectPath,
+ e.what());
+ }
+
+ return std::nullopt;
+ };
+
+ auto resolveUniqueProjectFileName = [&]() -> std::optional<std::string> {
+ if (!project || m_primaryFileName.empty())
+ return std::nullopt;
+
+ std::vector<Ref<ProjectFile>> matches;
+ for (const auto& projectFile : project->GetFiles())
+ if (projectFile->GetName() == m_primaryFileName)
+ matches.push_back(projectFile);
+
+ if (matches.empty())
+ return std::nullopt;
+
+ if (matches.size() > 1)
+ {
+ std::string paths;
+ for (const auto& match : matches)
+ {
+ if (!paths.empty())
+ paths += ", ";
+ paths += match->GetPathInProject();
+ }
+ m_logger->LogErrorF(
+ "Multiple project files are named '{}': {}. Provide loader.dsc.primaryFilePath with the project path to the correct primary shared cache file.",
+ m_primaryFileName, paths);
+ return std::string();
+ }
+
+ return storePrimaryProjectFile(matches[0]);
+ };
+
+ auto resolveMetadataPrimaryFilePath = [&]() -> std::optional<std::string> {
+ if (m_primaryFilePath.empty())
+ return std::nullopt;
+
+ if (project)
+ return resolveProjectFilePath(m_primaryFilePath);
+
+ auto path = ResolveRelativePath(m_primaryFilePath, databaseDir);
+ if (IsUsablePrimaryCachePath(path))
+ return path;
+ return std::nullopt;
+ };
auto settings = GetLoadSettings(GetTypeName());
if (settings && settings->Contains("loader.dsc.primaryFilePath"))
@@ -1035,7 +1160,17 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
if (!configuredPrimaryFilePath.empty())
{
settings->Reset("loader.dsc.primaryFilePath", this, SettingsResourceScope);
- if (!IsUsablePrimaryCachePath(configuredPrimaryFilePath))
+ if (project)
+ {
+ auto projectPathResult = resolveProjectFilePath(configuredPrimaryFilePath);
+ if (projectPathResult && projectPathResult->empty())
+ return std::nullopt;
+ if (projectPathResult)
+ return *projectPathResult;
+ }
+
+ auto resolvedConfiguredPath = ResolveRelativePath(configuredPrimaryFilePath, databaseDir);
+ if (!IsUsablePrimaryCachePath(resolvedConfiguredPath))
{
m_logger->LogErrorF(
"Configured primary shared cache file path is invalid: '{}'", configuredPrimaryFilePath);
@@ -1044,8 +1179,8 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
}
else
{
- SetPrimaryFileName(BaseFileName(configuredPrimaryFilePath));
- return configuredPrimaryFilePath;
+ SetPrimaryFileName(BaseFileName(resolvedConfiguredPath));
+ return resolvedConfiguredPath;
}
}
}
@@ -1087,10 +1222,17 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
return newPrimaryFilePath;
};
- // 1. Try and get the primary file path using `GetOriginalFilename`.
+ if (auto metadataPrimaryPath = resolveMetadataPrimaryFilePath())
+ {
+ if (metadataPrimaryPath->empty())
+ return std::nullopt;
+ return *metadataPrimaryPath;
+ }
+
+ // 1. Try the original filename for existing databases and direct opens of the primary file.
auto primaryFilePath = viewFile->GetOriginalFilename();
- // 2. If the original file name is not a usable file path then prompt the user to select one.
+ // 2. If the original filename is stale, try nearby files using stored metadata and legacy filename hints.
if (!IsUsablePrimaryCachePath(primaryFilePath))
{
std::vector<std::string> candidateNames;
@@ -1102,7 +1244,7 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
for (const auto& candidateName : candidateNames)
{
- auto candidatePath = (std::filesystem::path(viewFile->GetFilename()).parent_path() / candidateName).string();
+ auto candidatePath = (std::filesystem::path(databaseDir) / candidateName).string();
if (IsUsablePrimaryCachePath(candidatePath))
{
primaryFilePath = candidatePath;
@@ -1126,28 +1268,25 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
return std::nullopt;
}
- SetPrimaryFileName(BaseFileName(primaryFilePath));
+ storePrimaryFilePath(primaryFilePath);
}
- // 3. If we are not in a project, we can go ahead and return the file path, it does not need to be resolved from project.
- auto primaryProjectFile = viewFile->GetProjectFile();
- if (!primaryProjectFile)
+ // 3. If we are not in a project, the filesystem path is ready to use.
+ if (!currentProjectFile)
return primaryFilePath;
- auto project = primaryProjectFile->GetProject();
- auto primaryProjectFileName = primaryProjectFile->GetName();
- auto primaryProjectFilePath = primaryProjectFile->GetPathOnDisk();
+ auto primaryProjectFileName = currentProjectFile->GetName();
+ auto primaryProjectFilePath = currentProjectFile->GetPathOnDisk();
- // 4. If we are not a BNDB project file than we can return the path on disk as we are the primary file.
+ // 4. If the project file is the primary cache itself, use it and persist its project path.
if (primaryProjectFileName.find(".bndb") == std::string::npos)
{
- // Set the primary file name to the project file name so on subsequent loads we can pick it up.
- SetPrimaryFileName(primaryProjectFileName);
+ SetPrimaryFileLocation(currentProjectFile->GetPathInProject(), primaryProjectFileName);
return primaryProjectFilePath;
}
- // 5. If we are a BNDB project file the path must be resolved from the file name.
- auto primaryProjectFileFolder = primaryProjectFile->GetFolder();
+ // 5. Prefer a primary cache file with the stored basename in the same project folder as the BNDB.
+ auto primaryProjectFileFolder = currentProjectFile->GetFolder();
for (const auto& pj : project->GetFiles())
{
// Skip files not in the same folder.
@@ -1156,24 +1295,31 @@ std::optional<std::string> SharedCacheView::GetPrimaryFilePath()
// We are looking for the file with file name we stored in metadata.
if (pj->GetName() != m_primaryFileName)
continue;
- return pj->GetPathOnDisk();
+ return storePrimaryProjectFile(pj);
+ }
+
+ if (auto uniqueProjectPath = resolveUniqueProjectFileName())
+ {
+ if (uniqueProjectPath->empty())
+ return std::nullopt;
+ return *uniqueProjectPath;
}
if (IsUsablePrimaryCachePath(primaryFilePath) && BaseFileName(primaryFilePath) == m_primaryFileName)
return primaryFilePath;
- // 6. If we fail to resolve the project file given the `m_primaryFileName` than we fall back to asking the user.
+ // 6. If automatic project resolution failed, ask the user in UI mode. Headless callers must provide
+ // loader.dsc.primaryFilePath or arrange files so one of the automatic resolution paths works.
auto promptedPrimaryFilePath = promptForPrimaryFile();
if (!promptedPrimaryFilePath)
return std::nullopt;
std::string newPrimaryFilePath = *promptedPrimaryFilePath;
- // TODO: We likely want to verify that the project file exists in the same directory as the BNDB.
- // TODO: We currently require the database to exist in the same directory as the files.
- // Update the primary file name for later loads, otherwise we would keep prompting to select a file.
- primaryProjectFile = project->GetFileByPathOnDisk(newPrimaryFilePath);
+ // Persist a project-relative path when the selected file is in the project. External selections are
+ // allowed as an escape hatch, but only the basename is stored so local absolute paths are not synced.
+ auto primaryProjectFile = project->GetFileByPathOnDisk(newPrimaryFilePath);
if (primaryProjectFile)
- SetPrimaryFileName(primaryProjectFile->GetName());
+ SetPrimaryFileLocation(primaryProjectFile->GetPathInProject(), primaryProjectFile->GetName());
else
SetPrimaryFileName(BaseFileName(newPrimaryFilePath));
return newPrimaryFilePath;
@@ -1190,10 +1336,12 @@ Ref<Metadata> SharedCacheView::GetMetadata() const
// TODO: Refactor this to just "cache files" which is a new struct of:
// TODO: cache file name
+ // TODO: cache file path
// TODO: cache file UUID
// TODO: cache file entry type?
viewMeta["secondaryFileNames"] = new Metadata(secondaryFileNames);
viewMeta["primaryFileName"] = new Metadata(m_primaryFileName);
+ viewMeta["primaryFilePath"] = new Metadata(m_primaryFilePath);
return new Metadata(viewMeta);
}
@@ -1210,4 +1358,6 @@ void SharedCacheView::LoadMetadata(const Metadata &metadata)
if (viewMeta.find("primaryFileName") != viewMeta.end())
m_primaryFileName = viewMeta["primaryFileName"]->GetString();
+ if (viewMeta.find("primaryFilePath") != viewMeta.end())
+ m_primaryFilePath = viewMeta["primaryFilePath"]->GetString();
}