8#include <unordered_set>
10#include "absl/strings/str_format.h"
22std::time_t
ToTimeT(
const std::filesystem::file_time_type& ftime) {
23 using namespace std::chrono;
24 auto sctp = time_point_cast<system_clock::duration>(
25 ftime - std::filesystem::file_time_type::clock::now() +
27 return system_clock::to_time_t(sctp);
30std::string
DayKey(std::time_t timestamp) {
33 localtime_s(&local_tm, ×tamp);
35 localtime_r(×tamp, &local_tm);
38 std::strftime(buffer,
sizeof(buffer),
"%Y-%m-%d", &local_tm);
39 return std::string(buffer);
45 : toast_manager_(toast_manager) {}
49 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
51 if (filename.empty()) {
52 return absl::InvalidArgumentError(
"No filename provided");
58 Rom* rom,
const std::filesystem::path& collision_source) {
60 return absl::FailedPreconditionError(
"No ROM loaded to save");
66 if (!backup_status.ok()) {
69 absl::StrFormat(
"Backup failed: %s", backup_status.message()),
80 auto status = collision_source.empty()
85 absl::StrFormat(
"Failed to save ROM: %s", status.message()),
95 return absl::FailedPreconditionError(
"No ROM loaded to save");
97 if (filename.empty()) {
98 return absl::InvalidArgumentError(
"No filename provided for save as");
105 const std::string source_filename = rom->
filename();
109 if (!backup_status.ok()) {
112 absl::StrFormat(
"Backup failed: %s", backup_status.message()),
115 return backup_status;
128 absl::StrFormat(
"Failed to save ROM as: %s", status.message()),
141 const std::string& filename) {
143 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
145 if (filename.empty()) {
146 return absl::InvalidArgumentError(
"No filename provided");
149 std::string extension = std::filesystem::path(filename).extension().string();
151 if (extension ==
".yaze" || extension ==
".yazeproj" ||
152 extension ==
".zsproj") {
153 return absl::UnimplementedError(
"Project file loading not yet implemented");
161 return absl::FailedPreconditionError(
"No ROM loaded to backup");
164 const std::string source_filename = rom->
filename();
165 if (source_filename.empty()) {
166 return absl::InvalidArgumentError(
"ROM has no filename to backup");
174 std::filesystem::copy_file(source_filename, backup_filename,
175 std::filesystem::copy_options::overwrite_existing,
178 auto status = absl::InternalError(
179 absl::StrFormat(
"Failed to create backup: %s", ec.message()));
182 absl::StrFormat(
"Backup failed: %s", status.message()),
194 if (!prune_status.ok()) {
195 LOG_WARN(
"RomFileManager",
"Backup prune failed: %s",
196 prune_status.message().data());
199 return absl::OkStatus();
204 return absl::FailedPreconditionError(
"No valid ROM to validate");
207 if (rom->
size() < 512 * 1024 || rom->
size() > 8 * 1024 * 1024) {
208 return absl::InvalidArgumentError(
"ROM size is outside expected range");
210 if (rom->
title().empty()) {
211 return absl::InvalidArgumentError(
"ROM title is empty or invalid");
217 return absl::OkStatus();
232 const std::string& filename) {
234 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
237 return absl::InvalidArgumentError(
238 absl::StrFormat(
"Invalid ROM file: %s", filename));
245 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
260 return absl::OkStatus();
264 const std::string& original_filename)
const {
265 std::filesystem::path path(original_filename);
266 std::string stem = path.stem().string();
267 std::string extension = path.extension().string();
269 auto now = std::chrono::system_clock::now();
270 auto time_t = std::chrono::system_clock::to_time_t(now);
271 const auto ms_since_epoch =
272 std::chrono::duration_cast<std::chrono::milliseconds>(
273 now.time_since_epoch())
275 const auto ms_part = ms_since_epoch % 1000;
279 std::string filename = absl::StrFormat(
280 "%s_backup_%lld_%03lld%s", stem,
static_cast<long long>(time_t),
281 static_cast<long long>(ms_part), extension);
282 return (backup_dir / filename).string();
286 const std::string& original_filename)
const {
287 std::filesystem::path path(original_filename);
288 std::filesystem::path backup_dir = path.parent_path();
293 std::filesystem::create_directories(backup_dir, ec);
298 const std::string& rom_filename)
const {
299 std::vector<BackupEntry> backups;
300 if (rom_filename.empty()) {
304 std::filesystem::path rom_path(rom_filename);
305 const std::string stem = rom_path.stem().string();
306 const std::string extension = rom_path.extension().string();
307 const std::string prefix = stem +
"_backup_";
311 for (
const auto& entry :
312 std::filesystem::directory_iterator(backup_dir, ec)) {
313 if (ec || !entry.is_regular_file()) {
316 const auto path = entry.path();
317 if (path.extension() != extension) {
320 const std::string filename = path.filename().string();
321 if (filename.rfind(prefix, 0) != 0) {
326 backup.
path = path.string();
329 auto ftime = entry.last_write_time(ec);
333 backups.push_back(std::move(backup));
336 std::sort(backups.begin(), backups.end(),
338 return a.timestamp > b.timestamp;
344 const std::string& rom_filename)
const {
346 return absl::OkStatus();
351 return absl::OkStatus();
354 std::unordered_set<std::string> keep_paths;
355 const auto now = std::chrono::system_clock::now();
356 const auto keep_daily_days =
360 std::unordered_set<std::string> seen_days;
361 for (
const auto& backup : backups) {
362 if (backup.timestamp == 0) {
365 const auto backup_time =
366 std::chrono::system_clock::from_time_t(backup.timestamp);
367 if (now - backup_time > keep_daily_days) {
370 std::string day = DayKey(backup.timestamp);
371 if (seen_days.insert(day).second) {
372 keep_paths.insert(backup.path);
378 i < backups.size() &&
381 keep_paths.insert(backups[i].path);
384 for (
const auto& backup : backups) {
385 if (keep_paths.count(backup.path) > 0) {
388 std::error_code remove_ec;
389 std::filesystem::remove(backup.path, remove_ec);
391 LOG_WARN(
"RomFileManager",
"Failed to delete backup: %s",
392 backup.path.c_str());
396 return absl::OkStatus();
400 if (filename.empty()) {
405 if (!std::filesystem::exists(filename, ec) || ec) {
409 auto file_size = std::filesystem::file_size(filename, ec);
415 if (file_size < 512 * 1024 || file_size > 8 * 1024 * 1024) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
absl::Status CheckLayoutWritable() const
absl::Status SaveToFile(const SaveSettings &settings)
auto set_filename(std::string_view name)
std::string GenerateBackupFilename(const std::string &original_filename) const
absl::Status PruneBackups(const std::string &rom_filename) const
absl::Status OpenRomOrProject(Rom *rom, const std::string &filename)
absl::Status SaveRom(Rom *rom, const std::filesystem::path &collision_source={})
std::vector< BackupEntry > ListBackups(const std::string &rom_filename) const
absl::Status ValidateRom(Rom *rom)
RomFileManager(ToastManager *toast_manager)
std::string GetRomFilename(Rom *rom) const
absl::Status CreateBackup(Rom *rom)
int backup_retention_count_
absl::Status LoadRom(Rom *rom, const std::string &filename)
std::string backup_folder_
int backup_keep_daily_days_
ToastManager * toast_manager_
absl::Status LoadRomFromFile(Rom *rom, const std::string &filename)
bool IsValidRomFile(const std::string &filename) const
absl::Status SaveRomAs(Rom *rom, const std::string &filename)
bool IsRomLoaded(Rom *rom) const
std::filesystem::path GetBackupDirectory(const std::string &original_filename) const
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
#define LOG_WARN(category, format,...)
std::string DayKey(std::time_t timestamp)
std::time_t ToTimeT(const std::filesystem::file_time_type &ftime)
Editors are the view controllers for the application.
absl::Status SaveRomWithCollisionSource(Rom *rom, const fs::path &json_path)
#define RETURN_IF_ERROR(expr)