8#include <unordered_set>
10#include "absl/strings/str_format.h"
21std::time_t
ToTimeT(
const std::filesystem::file_time_type& ftime) {
22 using namespace std::chrono;
23 auto sctp = time_point_cast<system_clock::duration>(
24 ftime - std::filesystem::file_time_type::clock::now() +
26 return system_clock::to_time_t(sctp);
29std::string
DayKey(std::time_t timestamp) {
32 localtime_s(&local_tm, ×tamp);
34 localtime_r(×tamp, &local_tm);
37 std::strftime(buffer,
sizeof(buffer),
"%Y-%m-%d", &local_tm);
38 return std::string(buffer);
44 : toast_manager_(toast_manager) {}
48 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
50 if (filename.empty()) {
51 return absl::InvalidArgumentError(
"No filename provided");
58 return absl::FailedPreconditionError(
"No ROM loaded to save");
63 if (!backup_status.ok()) {
66 absl::StrFormat(
"Backup failed: %s", backup_status.message()),
80 absl::StrFormat(
"Failed to save ROM: %s", status.message()),
90 return absl::FailedPreconditionError(
"No ROM loaded to save");
92 if (filename.empty()) {
93 return absl::InvalidArgumentError(
"No filename provided for save as");
98 if (!backup_status.ok()) {
101 absl::StrFormat(
"Backup failed: %s", backup_status.message()),
104 return backup_status;
117 absl::StrFormat(
"Failed to save ROM as: %s", status.message()),
127 const std::string& filename) {
129 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
131 if (filename.empty()) {
132 return absl::InvalidArgumentError(
"No filename provided");
135 std::string extension = std::filesystem::path(filename).extension().string();
137 if (extension ==
".yaze" || extension ==
".yazeproj" ||
138 extension ==
".zsproj") {
139 return absl::UnimplementedError(
"Project file loading not yet implemented");
147 return absl::FailedPreconditionError(
"No ROM loaded to backup");
150 const std::string source_filename = rom->
filename();
151 if (source_filename.empty()) {
152 return absl::InvalidArgumentError(
"ROM has no filename to backup");
160 std::filesystem::copy_file(source_filename, backup_filename,
161 std::filesystem::copy_options::overwrite_existing,
164 auto status = absl::InternalError(
165 absl::StrFormat(
"Failed to create backup: %s", ec.message()));
168 absl::StrFormat(
"Backup failed: %s", status.message()),
180 if (!prune_status.ok()) {
181 LOG_WARN(
"RomFileManager",
"Backup prune failed: %s",
182 prune_status.message().data());
185 return absl::OkStatus();
190 return absl::FailedPreconditionError(
"No valid ROM to validate");
193 if (rom->
size() < 512 * 1024 || rom->
size() > 8 * 1024 * 1024) {
194 return absl::InvalidArgumentError(
"ROM size is outside expected range");
196 if (rom->
title().empty()) {
197 return absl::InvalidArgumentError(
"ROM title is empty or invalid");
203 return absl::OkStatus();
218 const std::string& filename) {
220 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
223 return absl::InvalidArgumentError(
224 absl::StrFormat(
"Invalid ROM file: %s", filename));
231 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
246 return absl::OkStatus();
250 const std::string& original_filename)
const {
251 std::filesystem::path path(original_filename);
252 std::string stem = path.stem().string();
253 std::string extension = path.extension().string();
255 auto now = std::chrono::system_clock::now();
256 auto time_t = std::chrono::system_clock::to_time_t(now);
257 const auto ms_since_epoch =
258 std::chrono::duration_cast<std::chrono::milliseconds>(
259 now.time_since_epoch())
261 const auto ms_part = ms_since_epoch % 1000;
265 std::string filename =
266 absl::StrFormat(
"%s_backup_%lld_%03lld%s", stem,
267 static_cast<long long>(time_t),
268 static_cast<long long>(ms_part), extension);
269 return (backup_dir / filename).string();
273 const std::string& original_filename)
const {
274 std::filesystem::path path(original_filename);
275 std::filesystem::path backup_dir = path.parent_path();
280 std::filesystem::create_directories(backup_dir, ec);
285 const std::string& rom_filename)
const {
286 std::vector<BackupEntry> backups;
287 if (rom_filename.empty()) {
291 std::filesystem::path rom_path(rom_filename);
292 const std::string stem = rom_path.stem().string();
293 const std::string extension = rom_path.extension().string();
294 const std::string prefix = stem +
"_backup_";
298 for (
const auto& entry : std::filesystem::directory_iterator(backup_dir, ec)) {
299 if (ec || !entry.is_regular_file()) {
302 const auto path = entry.path();
303 if (path.extension() != extension) {
306 const std::string filename = path.filename().string();
307 if (filename.rfind(prefix, 0) != 0) {
312 backup.
path = path.string();
315 auto ftime = entry.last_write_time(ec);
319 backups.push_back(std::move(backup));
322 std::sort(backups.begin(), backups.end(),
324 return a.timestamp > b.timestamp;
330 const std::string& rom_filename)
const {
332 return absl::OkStatus();
337 return absl::OkStatus();
340 std::unordered_set<std::string> keep_paths;
341 const auto now = std::chrono::system_clock::now();
342 const auto keep_daily_days =
346 std::unordered_set<std::string> seen_days;
347 for (
const auto& backup : backups) {
348 if (backup.timestamp == 0) {
351 const auto backup_time =
352 std::chrono::system_clock::from_time_t(backup.timestamp);
353 if (now - backup_time > keep_daily_days) {
356 std::string day = DayKey(backup.timestamp);
357 if (seen_days.insert(day).second) {
358 keep_paths.insert(backup.path);
363 for (
size_t i = 0; i < backups.size() &&
366 keep_paths.insert(backups[i].path);
369 for (
const auto& backup : backups) {
370 if (keep_paths.count(backup.path) > 0) {
373 std::error_code remove_ec;
374 std::filesystem::remove(backup.path, remove_ec);
376 LOG_WARN(
"RomFileManager",
"Failed to delete backup: %s",
377 backup.path.c_str());
381 return absl::OkStatus();
385 if (filename.empty()) {
390 if (!std::filesystem::exists(filename, ec) || ec) {
394 auto file_size = std::filesystem::file_size(filename, ec);
400 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 SaveToFile(const SaveSettings &settings)
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)
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)
absl::Status SaveRom(Rom *rom)
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)
#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.