143 lines
4.9 KiB
C++
143 lines
4.9 KiB
C++
#include "core/capture/capture_paths.h"
|
|
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <filesystem>
|
|
|
|
namespace reasampler::capture {
|
|
|
|
std::string normalizeSlashes(const std::string& path) {
|
|
std::string out = path;
|
|
for (char& c : out) {
|
|
if (c == '\\') c = '/';
|
|
}
|
|
// Strip a trailing slash but preserve a lone "/" (root).
|
|
if (out.size() > 1 && out.back() == '/') {
|
|
out.pop_back();
|
|
}
|
|
#ifdef _WIN32
|
|
for (char& c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
#endif
|
|
return out;
|
|
}
|
|
|
|
std::string sanitizeStem(const std::string& baseName) {
|
|
std::string out;
|
|
out.reserve(baseName.size());
|
|
for (unsigned char c : baseName) {
|
|
const bool keep = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
(c >= '0' && c <= '9') || c == '.' || c == '_' ||
|
|
c == '-';
|
|
out.push_back(keep ? static_cast<char>(c) : '_');
|
|
}
|
|
// Collapse to a stable default if nothing alnum survived.
|
|
bool hasAlnum = false;
|
|
for (unsigned char c : out) {
|
|
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
(c >= '0' && c <= '9')) {
|
|
hasAlnum = true;
|
|
break;
|
|
}
|
|
}
|
|
if (out.empty() || !hasAlnum) {
|
|
return "capture";
|
|
}
|
|
return out;
|
|
}
|
|
|
|
RenderPaths deriveRenderPaths(const std::string& absoluteDir,
|
|
const std::string& baseName,
|
|
const std::string& uniqueTag) {
|
|
std::string stem = sanitizeStem(baseName);
|
|
if (!uniqueTag.empty()) {
|
|
stem += "_" + sanitizeStem(uniqueTag);
|
|
}
|
|
|
|
RenderPaths r;
|
|
r.fileStem = stem; // stem only — REAPER appends the extension
|
|
r.fileName = stem + ".wav";
|
|
r.absoluteDir = normalizeSlashes(absoluteDir);
|
|
return r;
|
|
}
|
|
|
|
BankPaths deriveBankPaths(const std::string& projectDir,
|
|
const std::string& baseName,
|
|
const std::string& uniqueTag) {
|
|
const std::string dir = normalizeSlashes(projectDir);
|
|
|
|
// Precondition: caller must resolve a non-empty project directory — an
|
|
// empty one would otherwise fall back to a bare relative path (forbidden).
|
|
// Assert in debug; leave absoluteDir empty in release so a caller that
|
|
// ignores it fails at the render/stat step, not silently onto CWD.
|
|
assert(!dir.empty() && "deriveBankPaths: projectDir must not be empty");
|
|
|
|
const RenderPaths r = deriveRenderPaths(
|
|
dir.empty() ? std::string{} : dir + "/" + kBankSubfolder, baseName, uniqueTag);
|
|
|
|
BankPaths p;
|
|
p.fileStem = r.fileStem;
|
|
p.fileName = r.fileName;
|
|
p.relativePath = bankRelativeForName(r.fileName);
|
|
p.absoluteDir = r.absoluteDir;
|
|
return p;
|
|
}
|
|
|
|
std::string bankRelativeForName(const std::string& fileName) {
|
|
if (fileName.empty()) return {};
|
|
return std::string(kBankSubfolder) + "/" + fileName;
|
|
}
|
|
|
|
std::string resolveBankFile(const std::string& projectDir,
|
|
const std::string& relativePath) {
|
|
if (projectDir.empty() || relativePath.empty()) {
|
|
return {};
|
|
}
|
|
const std::string dir = normalizeSlashes(projectDir);
|
|
const std::string rel = normalizeSlashes(relativePath);
|
|
if (dir.empty() || rel.empty()) {
|
|
return {};
|
|
}
|
|
return dir + "/" + rel;
|
|
}
|
|
|
|
std::string projectDirOfRpp(const std::string& rppPath) {
|
|
if (rppPath.empty()) return {}; // unsaved project: keep empty, no fallback
|
|
std::string dir = std::filesystem::path(rppPath).parent_path().string();
|
|
return normalizeSlashes(dir);
|
|
}
|
|
|
|
BankRelocation deriveRelocationPlan(const std::string& oldProjectDir,
|
|
const std::string& newProjectDir) {
|
|
BankRelocation r;
|
|
if (oldProjectDir.empty() || newProjectDir.empty()) {
|
|
return r; // needed=false, empty dirs — nothing to relocate
|
|
}
|
|
const std::string oldDir = normalizeSlashes(oldProjectDir);
|
|
const std::string newDir = normalizeSlashes(newProjectDir);
|
|
|
|
r.oldBankDir = oldDir + "/" + kBankSubfolder;
|
|
r.newBankDir = newDir + "/" + kBankSubfolder;
|
|
r.needed = (oldDir != newDir); // Save-in-place leaves the dir unchanged
|
|
return r;
|
|
}
|
|
|
|
ProjectTransition classifyProjectTransition(bool sameProjectObject,
|
|
const std::string& lastGuid,
|
|
const std::string& lastPath,
|
|
const std::string& currentGuid,
|
|
const std::string& currentPath) {
|
|
// See capture_paths.h for the GUID-primary rationale and rule order.
|
|
if (currentGuid != lastGuid) {
|
|
return ProjectTransition::Load;
|
|
}
|
|
if (!sameProjectObject) {
|
|
return ProjectTransition::Load; // forked sibling: same GUID, different object
|
|
}
|
|
if (currentPath != lastPath) {
|
|
return ProjectTransition::SaveAsRelocate;
|
|
}
|
|
return ProjectTransition::NoOp;
|
|
}
|
|
|
|
} // namespace reasampler::capture
|