77
88struct BackupMetadata final {
99 typename Backup::TimePoint createTime = Backup::Clock::now();
10+ bool automated = false ;
1011};
1112
1213template <>
1314struct matjson ::Serialize<BackupMetadata> {
1415 static matjson::Value to_json (BackupMetadata const & meta) {
1516 return matjson::Object ({
16- { " create-time" , std::chrono::duration_cast<Backup::TimeUnit>(meta.createTime .time_since_epoch ()).count () }
17+ { " create-time" , std::chrono::duration_cast<Backup::TimeUnit>(meta.createTime .time_since_epoch ()).count () },
18+ { " automated" , meta.automated },
1719 });
1820 }
1921 static BackupMetadata from_json (matjson::Value const & value) {
2022 auto meta = BackupMetadata ();
2123 auto obj = value.as_object ();
24+
2225 meta.createTime = Backup::TimePoint (Backup::TimeUnit (obj[" create-time" ].as_int ()));
26+
27+ // Parsing should be as fault-tolerant as possible
28+ if (obj.contains (" automated" )) {
29+ meta.automated = obj[" automated" ].as_bool ();
30+ }
31+
2332 return meta;
2433 }
2534 static bool is_json (matjson::Value const & value) {
@@ -30,9 +39,15 @@ struct matjson::Serialize<BackupMetadata> {
3039GJGameLevel* Backup::getLevel () const {
3140 return m_level;
3241}
42+ GJGameLevel* Backup::getOriginalLevel () const {
43+ return m_forLevel;
44+ }
3345typename Backup::TimePoint Backup::getCreateTime () const {
3446 return m_createTime;
3547}
48+ bool Backup::isAutomated () const {
49+ return m_automated;
50+ }
3651
3752Result<> Backup::restoreThis () {
3853 // Add changes to memory
@@ -60,6 +75,15 @@ Result<> Backup::deleteThis() {
6075 }
6176 return Ok ();
6277}
78+ Result<> Backup::preserveAutomated () {
79+ m_automated = false ;
80+ auto metadata = BackupMetadata {
81+ .createTime = m_createTime,
82+ .automated = false ,
83+ };
84+ GEODE_UNWRAP (file::writeToJson (m_directory / " meta.json" , metadata).expect (" Unable to save metadata: {error}" ));
85+ return Ok ();
86+ }
6387
6488Result<std::shared_ptr<Backup>> Backup::load (ghc::filesystem::path const & dir, GJGameLevel* forLevel) {
6589 GEODE_UNWRAP_INTO (auto level, gmd::importGmdAsLevel (dir / " level.gmd" ).expect (" Unable to read level file: {error}" ));
@@ -68,8 +92,9 @@ Result<std::shared_ptr<Backup>> Backup::load(ghc::filesystem::path const& dir, G
6892 auto backup = std::make_shared<Backup>();
6993 backup->m_level = level;
7094 backup->m_forLevel = forLevel;
71- backup->m_createTime = meta.createTime ;
7295 backup->m_directory = dir;
96+ backup->m_createTime = meta.createTime ;
97+ backup->m_automated = meta.automated ;
7398 return Ok (backup);
7499}
75100std::vector<std::shared_ptr<Backup>> Backup::load (GJGameLevel* level) {
@@ -85,10 +110,15 @@ std::vector<std::shared_ptr<Backup>> Backup::load(GJGameLevel* level) {
85110 }
86111 res.push_back (*b);
87112 }
113+
114+ // This takes advantage of the fact that directories are sorted
115+ // alphabetically and the date format is oldest-newest when sorted that way
116+ // Might want to consider a proper sort-by-newest-first
88117 std::reverse (res.begin (), res.end ());
118+
89119 return res;
90120}
91- Result<> Backup::create (GJGameLevel* level) {
121+ Result<> Backup::create (GJGameLevel* level, bool automated ) {
92122 if (level->m_levelType != GJLevelType::Editor) {
93123 return Err (" Can not backup a non-editor level" );
94124 }
@@ -105,9 +135,45 @@ Result<> Backup::create(GJGameLevel* level) {
105135 GEODE_UNWRAP (gmd::exportLevelAsGmd (level, dir / " level.gmd" ).expect (" Unable to save level: {error}" ));
106136
107137 auto metadata = BackupMetadata {
108- .createTime = time
138+ .createTime = time,
139+ .automated = automated,
109140 };
110141 GEODE_UNWRAP (file::writeToJson (dir / " meta.json" , metadata).expect (" Unable to save metadata: {error}" ));
111142
112143 return Ok ();
113144}
145+ Result<> Backup::cleanAutomated (GJGameLevel* level) {
146+ std::vector<std::shared_ptr<Backup>> automated;
147+ for (auto folder : file::readDirectory (save::getCurrentLevelSaveDir (level) / " backups" ).unwrapOrDefault ()) {
148+ if (!ghc::filesystem::is_directory (folder)) {
149+ continue ;
150+ }
151+ auto b = Backup::load (folder, level);
152+ if (!b) {
153+ continue ;
154+ }
155+ auto backup = *b;
156+ if (backup->m_automated ) {
157+ automated.push_back (backup);
158+ }
159+ }
160+
161+ // This takes advantage of the fact that directories are sorted
162+ // alphabetically and the date format is oldest-newest when sorted that way
163+ // Might want to consider a proper sort-by-newest-first
164+ std::reverse (automated.begin (), automated.end ());
165+
166+ constexpr size_t MAX_AUTOMATED_COUNT = 3 ;
167+
168+ // Do the cleanup
169+ size_t ix = 0 ;
170+ for (auto backup : automated) {
171+ // Keep only the MAX_AUTOMATED_COUNT newest automated backups
172+ if (++ix > MAX_AUTOMATED_COUNT) {
173+ GEODE_UNWRAP (backup->deleteThis ());
174+ }
175+ }
176+ automated.erase (automated.begin () + MAX_AUTOMATED_COUNT, automated.end ());
177+
178+ return Ok ();
179+ }
0 commit comments