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");
99 const std::string source_filename = rom->
filename();
103 if (!backup_status.ok()) {
106 absl::StrFormat(
"Backup failed: %s", backup_status.message()),
109 return backup_status;
122 absl::StrFormat(
"Failed to save ROM as: %s", status.message()),
135 const std::string& filename) {
137 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
139 if (filename.empty()) {
140 return absl::InvalidArgumentError(
"No filename provided");
143 std::string extension = std::filesystem::path(filename).extension().string();
145 if (extension ==
".yaze" || extension ==
".yazeproj" ||
146 extension ==
".zsproj") {
147 return absl::UnimplementedError(
"Project file loading not yet implemented");
155 return absl::FailedPreconditionError(
"No ROM loaded to backup");
158 const std::string source_filename = rom->
filename();
159 if (source_filename.empty()) {
160 return absl::InvalidArgumentError(
"ROM has no filename to backup");
168 std::filesystem::copy_file(source_filename, backup_filename,
169 std::filesystem::copy_options::overwrite_existing,
172 auto status = absl::InternalError(
173 absl::StrFormat(
"Failed to create backup: %s", ec.message()));
176 absl::StrFormat(
"Backup failed: %s", status.message()),
188 if (!prune_status.ok()) {
189 LOG_WARN(
"RomFileManager",
"Backup prune failed: %s",
190 prune_status.message().data());
193 return absl::OkStatus();
198 return absl::FailedPreconditionError(
"No valid ROM to validate");
201 if (rom->
size() < 512 * 1024 || rom->
size() > 8 * 1024 * 1024) {
202 return absl::InvalidArgumentError(
"ROM size is outside expected range");
204 if (rom->
title().empty()) {
205 return absl::InvalidArgumentError(
"ROM title is empty or invalid");
211 return absl::OkStatus();
226 const std::string& filename) {
228 return absl::InvalidArgumentError(
"ROM pointer cannot be null");
231 return absl::InvalidArgumentError(
232 absl::StrFormat(
"Invalid ROM file: %s", filename));
239 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
254 return absl::OkStatus();
258 const std::string& original_filename)
const {
259 std::filesystem::path path(original_filename);
260 std::string stem = path.stem().string();
261 std::string extension = path.extension().string();
263 auto now = std::chrono::system_clock::now();
264 auto time_t = std::chrono::system_clock::to_time_t(now);
265 const auto ms_since_epoch =
266 std::chrono::duration_cast<std::chrono::milliseconds>(
267 now.time_since_epoch())
269 const auto ms_part = ms_since_epoch % 1000;
273 std::string filename = absl::StrFormat(
274 "%s_backup_%lld_%03lld%s", stem,
static_cast<long long>(time_t),
275 static_cast<long long>(ms_part), extension);
276 return (backup_dir / filename).string();
280 const std::string& original_filename)
const {
281 std::filesystem::path path(original_filename);
282 std::filesystem::path backup_dir = path.parent_path();
287 std::filesystem::create_directories(backup_dir, ec);
292 const std::string& rom_filename)
const {
293 std::vector<BackupEntry> backups;
294 if (rom_filename.empty()) {
298 std::filesystem::path rom_path(rom_filename);
299 const std::string stem = rom_path.stem().string();
300 const std::string extension = rom_path.extension().string();
301 const std::string prefix = stem +
"_backup_";
305 for (
const auto& entry :
306 std::filesystem::directory_iterator(backup_dir, ec)) {
307 if (ec || !entry.is_regular_file()) {
310 const auto path = entry.path();
311 if (path.extension() != extension) {
314 const std::string filename = path.filename().string();
315 if (filename.rfind(prefix, 0) != 0) {
320 backup.
path = path.string();
323 auto ftime = entry.last_write_time(ec);
327 backups.push_back(std::move(backup));
330 std::sort(backups.begin(), backups.end(),
332 return a.timestamp > b.timestamp;
338 const std::string& rom_filename)
const {
340 return absl::OkStatus();
345 return absl::OkStatus();
348 std::unordered_set<std::string> keep_paths;
349 const auto now = std::chrono::system_clock::now();
350 const auto keep_daily_days =
354 std::unordered_set<std::string> seen_days;
355 for (
const auto& backup : backups) {
356 if (backup.timestamp == 0) {
359 const auto backup_time =
360 std::chrono::system_clock::from_time_t(backup.timestamp);
361 if (now - backup_time > keep_daily_days) {
364 std::string day = DayKey(backup.timestamp);
365 if (seen_days.insert(day).second) {
366 keep_paths.insert(backup.path);
372 i < backups.size() &&
375 keep_paths.insert(backups[i].path);
378 for (
const auto& backup : backups) {
379 if (keep_paths.count(backup.path) > 0) {
382 std::error_code remove_ec;
383 std::filesystem::remove(backup.path, remove_ec);
385 LOG_WARN(
"RomFileManager",
"Failed to delete backup: %s",
386 backup.path.c_str());
390 return absl::OkStatus();
394 if (filename.empty()) {
399 if (!std::filesystem::exists(filename, ec) || ec) {
403 auto file_size = std::filesystem::file_size(filename, ec);
409 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)
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)
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.