Land the package filesystem shell: streaming atomic package_io, journaled rollback carve-out, asymmetric platform pickers

This commit is contained in:
2026-08-02 07:36:25 -04:00
parent 09a9ef838f
commit 41a3016e63
11 changed files with 914 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
// package_pickers.cpp — see package_pickers.h. Export is native rather than REAPER
// API: the SDK's newer GetUserFileName(mode=0) could save, but it resolves to null
// on older REAPER builds this extension still loads in, while GetSaveFileNameW /
// BrowseForSaveFile are always present. main.cpp owns the REAPER API pointers; this
// TU gets them extern.
#include "shell/package/package_pickers.h"
#ifdef _WIN32
#include <windows.h>
#include <commdlg.h>
#else
#include "swell/swell.h"
#endif
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_GetUserFileNameForRead
#define REAPERAPI_WANT_GetMainHwnd
#include "reaper_plugin_functions.h"
namespace reasampler {
bool pickPackageForImport(std::string& outAbsPath) {
outAbsPath.clear();
if (!GetUserFileNameForRead) return false;
char buf[4096];
buf[0] = '\0';
// defext spelled without the dot, matching Win32 lpstrDefExt. [verify — DAW]
// whether REAPER's picker applies it to the shown filter.
if (!GetUserFileNameForRead(buf, "Import bank package", "rsbank")) return false;
outAbsPath = buf;
return !outAbsPath.empty();
}
#ifdef _WIN32
bool pickPackageSavePath(const std::string& suggestedFileName, std::string& outAbsPath) {
outAbsPath.clear();
wchar_t file[4096];
file[0] = L'\0';
if (!suggestedFileName.empty()) {
const int wrote = MultiByteToWideChar(CP_UTF8, 0, suggestedFileName.c_str(),
-1, file, 4096);
if (wrote <= 0) file[0] = L'\0'; // unconvertible suggestion -> empty box
}
OPENFILENAMEW ofn{};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = GetMainHwnd ? GetMainHwnd() : nullptr;
ofn.lpstrFilter =
L"ReaSampler bank package (*.rsbank)\0*.rsbank\0All files (*.*)\0*.*\0";
ofn.lpstrFile = file;
ofn.nMaxFile = 4096;
ofn.lpstrTitle = L"Export bank package";
ofn.lpstrDefExt = L"rsbank";
// NOCHANGEDIR: REAPER's process-wide working directory is not ours to move.
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR |
OFN_HIDEREADONLY;
if (!GetSaveFileNameW(&ofn)) return false;
const int need =
WideCharToMultiByte(CP_UTF8, 0, file, -1, nullptr, 0, nullptr, nullptr);
if (need <= 0) return false;
std::string utf8(static_cast<std::size_t>(need), '\0');
WideCharToMultiByte(CP_UTF8, 0, file, -1, &utf8[0], need, nullptr, nullptr);
if (!utf8.empty() && utf8.back() == '\0') utf8.pop_back();
outAbsPath = std::move(utf8);
return !outAbsPath.empty();
}
#else // SWELL (macOS / Linux)
bool pickPackageSavePath(const std::string& suggestedFileName, std::string& outAbsPath) {
outAbsPath.clear();
// GetOpenFileName-style pair list; the literal's implicit terminator supplies the
// closing double-NUL. [verify — DAW] the exact filter strings SWELL accepts.
static const char kExtList[] = "ReaSampler bank package (*.rsbank)\0*.rsbank\0";
char fn[4096];
fn[0] = '\0';
if (!BrowseForSaveFile("Export bank package", nullptr,
suggestedFileName.empty() ? nullptr
: suggestedFileName.c_str(),
kExtList, fn, static_cast<int>(sizeof(fn)))) {
return false;
}
outAbsPath = fn;
return !outAbsPath.empty();
}
#endif
} // namespace reasampler