// shell/package/export_bank — the promptless bank-export verb: survey, digest, // stream, commit. No prompts and no message boxes (shell/actions/ // package_export_action is the skin). The session arrives CONST, which is how "an // export writes no ext state, opens no undo point and never bumps the bank // generation" is enforced rather than remembered — those three acts // (saveToActiveProject, bumpBankGeneration, writeAssignmentRequest) are exactly the // session members that are non-const (session.h:113,108,147). Constness does not // block every mutation, though: pruneReclaim (session.h:140-141) is const and is // the system's sole file-deletion path — irrelevant to export, but not something a // const session forbids in general. Blocking I/O: UI-thread actions only. #pragma once #include #include #include #include #include "core/package/bank_package.h" #include "core/package/export_plan.h" namespace reasampler { class ReaSamplerSession; struct ExportRequest { std::string projectDir; // absolute; the root the index's relative paths hang off std::string bankId; std::string destAbsPath; // the .rsbank to write std::int64_t exportTimestamp = 0; // manifest envelope; the caller's clock read // Both default false and are set ONLY after the skin's explicit confirm: one // lists what is absent, the other names the destination being replaced. bool allowIncomplete = false; bool allowOverwrite = false; }; enum class ExportStatus { Written, NoSuchBank, NoProjectDir, RefusedIncomplete, RefusedUnrepresentable, RefusedDestinationExists, SourceReadFailed, // a file the plan classified Present would not read, or is empty SourceChanged, // a payload's bytes moved between the digest pass and the stream pass EncodeFailed, WriteFailed, }; struct ExportOutcome { ExportStatus status = ExportStatus::WriteFailed; std::size_t entriesWritten = 0; std::uint64_t bytesWritten = 0; std::string bankDisplayName; std::vector excluded; std::string offendingName; // the entry a SourceReadFailed / SourceChanged names }; struct ExportSurvey { bool bankFound = false; package::ExportPlan plan; }; // Report-before-acting: the same plan exportBank recomputes, with nothing written. // Read-only against both the project and the filesystem. ExportSurvey surveyBankExport(const ReaSamplerSession& session, const std::string& projectDir, const std::string& bankId); // Fills each manifest entry's byteLength and byteHash from its source file — the // digest pass, one payload in memory at a time. False with `outFailedName` set when a // source will not read or is empty; a zero-length entry cannot round-trip the // format's own seam, so it is a failure here rather than an entry. bool digestSources(package::PackageManifest& manifest, const std::vector& sourceAbsPaths, std::string& outFailedName); // Streams one package to `destAbsPath`: the encoded prefix, then each payload re-read // from `sourceAbsPaths` (parallel to `manifest.entries`) and re-checked against the // length and digest recorded for it before it is appended — so the digest the // manifest claims describes the bytes actually written, not the bytes a concurrent // edit replaced. Any failure abandons the writer, leaving the destination absent or // holding its prior contents. // // Public because that atomicity is this function's property: proving it needs a // failure injected mid-stream, which is a call to this seam, not to exportBank. ExportOutcome writePackageFile(const package::EncodedPackage& encoded, const package::PackageManifest& manifest, const std::vector& sourceAbsPaths, const std::string& destAbsPath); // The verb: plan, gate on the verdict and the destination, digest, encode, stream. ExportOutcome exportBank(const ReaSamplerSession& session, const ExportRequest& req); } // namespace reasampler