import: a .rsbank lands as a new bank, whole or not at all

Four collisions answered explicitly: ids reminted, names never overwritten,
content deduped before the write, bank name auto-suffixed. Degraded ledger
refuses before the picker.
This commit is contained in:
2026-08-02 13:21:48 -04:00
parent 33ea95078d
commit a927dad2f4
26 changed files with 1689 additions and 24 deletions
+94
View File
@@ -0,0 +1,94 @@
// import_bank.cpp — see import_bank.h for the contract. The REAPER-facing half of the
// import: the project directory, the minted bank id, the birth records, and the one
// undo-batched persist. Every decision it makes is in import_landing / import_plan.
//
// main.cpp owns the API pointers; this TU gets them extern. DAW-verified, not unit tested.
#include "shell/package/import_bank.h"
#include <cstdint>
#include <ctime>
#include <string>
#include <vector>
#include "core/capture/capture_paths.h" // projectDirOfRpp
#include "shell/bank_ops/bank_ops.h" // persistBankOp — one bank op is one Ctrl-Z
#include "shell/persist/session.h"
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_EnumProjects
#define REAPERAPI_WANT_genGuid
#define REAPERAPI_WANT_guidToString
#include "reaper_plugin_functions.h"
namespace reasampler {
namespace {
std::string activeProjectDir() {
std::vector<char> buf(4096, '\0');
EnumProjects(-1, buf.data(), static_cast<int>(buf.size()));
return capture::projectDirOfRpp(std::string(buf.data()));
}
// The model mints no ids (it stays pure and deterministic), so the shell does — the
// same GUID pair bankOpCreate uses.
std::string mintBankId() {
GUID g{};
genGuid(&g);
char buf[64] = {0}; // guidToString needs >=64 chars (SDK contract)
guidToString(&g, buf);
return std::string(buf);
}
void fillPlanCounts(ImportBankResult& out, const package::ImportPlan& plan) {
out.bankDisplayName = plan.bankDisplayName;
out.seedBankName = plan.seedBankName;
out.bankNameAdjusted = plan.bankNameAdjusted;
out.landedCount = plan.landCount;
out.renamedCount = plan.renameCount;
out.collapsedCount = plan.collapseCount;
}
} // namespace
ImportBankResult importBankPackage(ReaSamplerSession& session,
const std::string& packageAbsPath) {
ImportBankResult out;
// A per-import disambiguator, the same shape capture and ingest file under.
const std::string uniqueTag =
std::to_string(static_cast<std::int64_t>(std::time(nullptr)));
LandedFileJournal journal;
const ImportLanding landing = landPackage(packageAbsPath, activeProjectDir(),
session.book(), uniqueTag, journal);
out.outcome = landing.outcome;
out.header = landing.header;
out.failedEntryName = landing.failedEntryName;
out.rollback = landing.rollback;
fillPlanCounts(out, landing.plan);
if (landing.outcome != ImportOutcome::Landed) return out;
const bool applied = applyImportedBank(
session.book(), mintBankId(), landing.plan,
[&session](const model::Sample& s) {
session.recordCreated(s, tracking::OriginKind::PackageImport);
});
if (!applied) {
out.outcome = ImportOutcome::IndexRejected;
out.rollback = journal.rollback();
return out;
}
// Generation bump + persist ride inside one undo block, so a Ctrl-Z takes the whole
// import back out of the index. It does NOT un-write the files — the summary says so.
persistBankOp(session, "ReaSampler: import bank package", /*bumpGeneration=*/true);
// Only now: the files are referenced, so prune's self-cleanup carve-out no longer
// covers them (see package_rollback.h).
journal.markIndexCommitted();
return out;
}
} // namespace reasampler