yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_sandbox_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdlib>
5
6#include "absl/status/status.h"
7#include "absl/status/statusor.h"
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
12#include "util/macro.h"
13#include "util/platform_paths.h"
14
15namespace yaze {
16namespace cli {
17
18namespace {
19
20std::filesystem::path DetermineDefaultRoot() {
21 if (const char* env_root = std::getenv("YAZE_SANDBOX_ROOT")) {
22 return std::filesystem::path(env_root);
23 }
24 auto app_data = util::PlatformPaths::GetAppDataSubdirectory("sandboxes");
25 if (app_data.ok()) {
26 return *app_data;
27 }
28 std::error_code ec;
29 auto temp_dir = std::filesystem::temp_directory_path(ec);
30 if (ec) {
31 // Fallback to current working directory if temp is unavailable.
32 return std::filesystem::current_path() / "yaze" / "sandboxes";
33 }
34 return temp_dir / "yaze" / "sandboxes";
35}
36
37std::filesystem::path ResolveUniqueDirectory(const std::filesystem::path& root,
38 absl::string_view id) {
39 return root / std::string(id);
40}
41
42} // namespace
43
45 static RomSandboxManager* instance = new RomSandboxManager();
46 return *instance;
47}
48
50 : root_directory_(DetermineDefaultRoot()) {}
51
52void RomSandboxManager::SetRootDirectory(const std::filesystem::path& root) {
53 std::lock_guard<std::mutex> lock(mutex_);
54 root_directory_ = root;
56}
57
58const std::filesystem::path& RomSandboxManager::RootDirectory() const {
59 return root_directory_;
60}
61
63 std::error_code ec;
64 if (!std::filesystem::exists(root_directory_, ec)) {
65 if (!std::filesystem::create_directories(root_directory_, ec) && ec) {
66 return absl::InternalError(
67 absl::StrCat("Failed to create sandbox root at ",
68 root_directory_.string(), ": ", ec.message()));
69 }
70 }
71 return absl::OkStatus();
72}
73
75 absl::Time now = absl::Now();
76 std::string time_component =
77 absl::FormatTime("%Y%m%dT%H%M%S", now, absl::LocalTimeZone());
78 ++sequence_;
79 return absl::StrCat(time_component, "-", sequence_);
80}
81
82absl::StatusOr<RomSandboxManager::SandboxMetadata>
83RomSandboxManager::CreateSandbox(Rom& rom, absl::string_view description) {
84 if (!rom.is_loaded()) {
85 return absl::FailedPreconditionError(
86 "Cannot create sandbox: ROM is not loaded");
87 }
88
89 std::filesystem::path source_path(rom.filename());
90 if (source_path.empty()) {
91 return absl::FailedPreconditionError(
92 "Cannot create sandbox: ROM filename is empty");
93 }
94
95 std::unique_lock<std::mutex> lock(mutex_);
97
98 std::string id = GenerateSandboxIdLocked();
99 std::filesystem::path sandbox_dir =
100 ResolveUniqueDirectory(root_directory_, id);
101 lock.unlock();
102
103 std::error_code ec;
104 if (!std::filesystem::create_directories(sandbox_dir, ec) && ec) {
105 return absl::InternalError(
106 absl::StrCat("Failed to create sandbox directory at ",
107 sandbox_dir.string(), ": ", ec.message()));
108 }
109
110 std::filesystem::path sandbox_rom_path = sandbox_dir / source_path.filename();
111
112 Rom::SaveSettings settings;
113 settings.filename = sandbox_rom_path.string();
114 settings.save_new = false;
115 settings.backup = false;
116
117 // Materializing a sandbox is not a save of the caller's source ROM. Preserve
118 // its editor state even though SaveToFile clears dirty on a successful write.
119 const bool source_dirty = rom.dirty();
120 absl::Status save_status = rom.SaveToFile(settings);
121 rom.set_dirty(source_dirty);
122 if (!save_status.ok()) {
123 std::error_code cleanup_ec;
124 std::filesystem::remove_all(sandbox_dir, cleanup_ec);
125 return save_status;
126 }
127
128 lock.lock();
130 .id = id,
131 .directory = sandbox_dir,
132 .rom_path = sandbox_rom_path,
133 .source_rom = source_path.string(),
134 .description = std::string(description),
135 .created_at = absl::Now(),
136 };
138
139 return sandboxes_.at(id);
140}
141
142absl::StatusOr<RomSandboxManager::SandboxMetadata>
144 std::lock_guard<std::mutex> lock(mutex_);
145 if (!active_sandbox_id_.has_value()) {
146 return absl::NotFoundError("No active sandbox");
147 }
148 auto it = sandboxes_.find(*active_sandbox_id_);
149 if (it == sandboxes_.end()) {
150 return absl::NotFoundError("Active sandbox metadata missing");
151 }
152 return it->second;
153}
154
155absl::StatusOr<std::filesystem::path> RomSandboxManager::ActiveSandboxRomPath()
156 const {
157 ASSIGN_OR_RETURN(auto meta, ActiveSandbox());
158 return meta.rom_path;
159}
160
161std::vector<RomSandboxManager::SandboxMetadata>
163 std::lock_guard<std::mutex> lock(mutex_);
164 std::vector<SandboxMetadata> list;
165 list.reserve(sandboxes_.size());
166 for (const auto& [_, metadata] : sandboxes_) {
167 list.push_back(metadata);
168 }
169 std::sort(list.begin(), list.end(),
170 [](const SandboxMetadata& a, const SandboxMetadata& b) {
171 return a.created_at < b.created_at;
172 });
173 return list;
174}
175
176absl::Status RomSandboxManager::RemoveSandbox(const std::string& id) {
177 std::lock_guard<std::mutex> lock(mutex_);
178 auto it = sandboxes_.find(id);
179 if (it == sandboxes_.end()) {
180 return absl::NotFoundError("Sandbox not found");
181 }
182 std::error_code ec;
183 std::filesystem::remove_all(it->second.directory, ec);
184 if (ec) {
185 return absl::InternalError(
186 absl::StrCat("Failed to remove sandbox directory: ", ec.message()));
187 }
188 sandboxes_.erase(it);
189 if (active_sandbox_id_.has_value() && *active_sandbox_id_ == id) {
190 active_sandbox_id_.reset();
191 }
192 return absl::OkStatus();
193}
194
196 absl::Duration max_age) {
197 std::vector<std::string> to_remove;
198 {
199 std::lock_guard<std::mutex> lock(mutex_);
200 absl::Time threshold = absl::Now() - max_age;
201 for (const auto& [id, metadata] : sandboxes_) {
202 if (metadata.created_at < threshold) {
203 to_remove.push_back(id);
204 }
205 }
206 }
207
208 int removed = 0;
209 for (const auto& id : to_remove) {
210 absl::Status status = RemoveSandbox(id);
211 if (!status.ok()) {
212 return status;
213 }
214 ++removed;
215 }
216 return removed;
217}
218
219} // namespace cli
220} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
auto filename() const
Definition rom.h:157
void set_dirty(bool dirty)
Definition rom.h:146
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:371
bool dirty() const
Definition rom.h:145
bool is_loaded() const
Definition rom.h:144
absl::StatusOr< SandboxMetadata > CreateSandbox(Rom &rom, absl::string_view description)
absl::Status RemoveSandbox(const std::string &id)
void SetRootDirectory(const std::filesystem::path &root)
absl::StatusOr< SandboxMetadata > ActiveSandbox() const
absl::StatusOr< int > CleanupOlderThan(absl::Duration max_age)
std::filesystem::path root_directory_
std::vector< SandboxMetadata > ListSandboxes() const
static RomSandboxManager & Instance()
std::optional< std::string > active_sandbox_id_
absl::StatusOr< std::filesystem::path > ActiveSandboxRomPath() const
const std::filesystem::path & RootDirectory() const
std::unordered_map< std::string, SandboxMetadata > sandboxes_
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::filesystem::path ResolveUniqueDirectory(const std::filesystem::path &root, absl::string_view id)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::string filename
Definition rom.h:33