Plan save-version targets in app core

This commit is contained in:
2026-06-02 22:58:28 +02:00
parent b349f24931
commit 1df506a176
9 changed files with 263 additions and 26 deletions

View File

@@ -2,6 +2,8 @@
#include "foundation/result.h"
#include <cctype>
#include <cstdio>
#include <string>
#include <string_view>
#include <utility>
@@ -50,6 +52,11 @@ struct DocumentFileTarget {
std::string path;
};
struct DocumentVersionTarget {
std::string name;
std::string path;
};
[[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept
{
return has_unsaved_changes
@@ -142,4 +149,82 @@ struct DocumentFileTarget {
: DocumentFileWriteDecision::save_now;
}
[[nodiscard]] inline bool has_legacy_two_character_version_suffix(std::string_view document_name) noexcept
{
const auto dot = document_name.rfind('.');
if (dot == std::string_view::npos || dot + 3 != document_name.size()) {
return false;
}
const auto is_word = [](char ch) noexcept {
return std::isalnum(static_cast<unsigned char>(ch)) != 0 || ch == '_';
};
return is_word(document_name[dot + 1]) && is_word(document_name[dot + 2]);
}
[[nodiscard]] inline int legacy_version_number(std::string_view suffix) noexcept
{
int value = 0;
for (const char ch : suffix) {
if (ch < '0' || ch > '9') {
break;
}
value = value * 10 + (ch - '0');
}
return value;
}
[[nodiscard]] inline std::string make_legacy_version_name(std::string_view base_name, int version)
{
char suffix[4] {};
std::snprintf(suffix, sizeof(suffix), ".%02d", version);
std::string name;
name.reserve(base_name.size() + 3);
name += base_name;
name += suffix;
return name;
}
template <typename ExistsPredicate>
[[nodiscard]] pp::foundation::Result<DocumentVersionTarget> find_next_document_version_target(
std::string_view directory,
std::string_view document_name,
ExistsPredicate&& exists)
{
if (directory.empty()) {
return pp::foundation::Result<DocumentVersionTarget>::failure(
pp::foundation::Status::invalid_argument("directory must not be empty"));
}
if (document_name.empty()) {
return pp::foundation::Result<DocumentVersionTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
int current = 0;
std::string_view base = document_name;
if (has_legacy_two_character_version_suffix(document_name)) {
const auto dot = document_name.rfind('.');
base = document_name.substr(0, dot);
current = legacy_version_number(document_name.substr(dot + 1));
}
for (int version = current + 1; version < 99; ++version) {
DocumentVersionTarget target;
target.name = make_legacy_version_name(base, version);
target.path.reserve(directory.size() + target.name.size() + 5);
target.path += directory;
target.path += "/";
target.path += target.name;
target.path += ".ppi";
if (!exists(target.path)) {
return pp::foundation::Result<DocumentVersionTarget>::success(std::move(target));
}
}
return pp::foundation::Result<DocumentVersionTarget>::failure(
pp::foundation::Status::out_of_range("no available document version target"));
}
}

View File

@@ -292,30 +292,19 @@ void App::dialog_save_ver()
return;
}
int current = 0;
std::string next = doc_name + ".01";
std::string base = doc_name;
std::regex r(R"((.*)\.(\w{2})$)");
std::smatch m;
if (std::regex_search(doc_name, m, r))
{
base = m[1].str();
current = atoi(m[2].str().c_str());
const auto target = pp::app::find_next_document_version_target(
doc_dir,
doc_name,
[](const std::string& path) {
return Asset::exist(path);
});
if (!target) {
message_box("Saving Error", target.status().message);
return;
}
for (int i = current + 1; i < 99; i++)
{
static char tmp_name[256];
sprintf(tmp_name, "%s.%02d", base.c_str(), i);
next = tmp_name;
if (Asset::exist(doc_dir + "/" + next + ".ppi"))
continue;
break;
}
doc_name = next;
doc_path = doc_dir + "/" + next + ".ppi";
doc_name = target.value().name;
doc_path = target.value().path;
canvas->m_canvas->m_unsaved = true;
title_update();
canvas->m_canvas->project_save(doc_path);