Skip to content

Commit

Permalink
Merge pull request scp-fs2open#6120 from Goober5000/fred_autorecover
Browse files Browse the repository at this point in the history
Fred autorecover
  • Loading branch information
Goober5000 authored Jun 1, 2024
2 parents e4b5d5f + bc4cc59 commit b5351c7
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 31 deletions.
1 change: 1 addition & 0 deletions code/cfile/cfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ struct CFileLocation {
SCP_string full_name;
size_t size = 0;
size_t offset = 0;
time_t m_time = 0;
const void* data_ptr = nullptr;

explicit CFileLocation(bool found_in = false) : found(found_in) {}
Expand Down
21 changes: 21 additions & 0 deletions code/cfile/cfilesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,22 @@ static bool sub_path_match(const SCP_string &search, const SCP_string &index)
return !stricmp(search.c_str(), index.c_str());
}

static time_t get_mtime(int fd)
{
#ifdef _WIN32
struct _stat buf;
#define fstat _fstat
#else
struct stat buf;
#endif

if (fstat(fd, &buf) != 0) {
return 0;
}

return buf.st_mtime;
}

/**
* Searches for a file.
*
Expand Down Expand Up @@ -1270,6 +1286,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t
if (fp) {
CFileLocation res(true);
res.size = static_cast<size_t>(filelength(fileno(fp)));
res.m_time = get_mtime(fileno(fp));

fclose(fp);

Expand Down Expand Up @@ -1332,6 +1349,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t
if (fp) {
CFileLocation res(true);
res.size = static_cast<size_t>(filelength( fileno(fp) ));
res.m_time = get_mtime(fileno(fp));

fclose(fp);

Expand Down Expand Up @@ -1388,6 +1406,7 @@ CFileLocation cf_find_file_location(const char* filespec, int pathtype, uint32_t
res.offset = (size_t)f->pack_offset;
res.data_ptr = f->data;
res.name_ext = f->name_ext;
res.m_time = f->write_time;

if (f->data != nullptr) {
// This is an in-memory file so we just copy the pathtype name + file name
Expand Down Expand Up @@ -1523,6 +1542,7 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n
CFileLocationExt res(cur_ext);
res.found = true;
res.size = static_cast<size_t>(filelength( fileno(fp) ));
res.m_time = get_mtime(fileno(fp));

fclose(fp);

Expand Down Expand Up @@ -1611,6 +1631,7 @@ CFileLocationExt cf_find_file_location_ext(const char *filename, const int ext_n
res.offset = (size_t)f->pack_offset;
res.data_ptr = f->data;
res.name_ext = f->name_ext;
res.m_time = f->write_time;

if (f->data != nullptr) {
// This is an in-memory file so we just copy the pathtype name + file name
Expand Down
66 changes: 59 additions & 7 deletions fred2/freddoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ CFREDDoc::CFREDDoc() {

FREDDoc_ptr = this;

for (i = 0; i < BACKUP_DEPTH; i++)
for (i = 0; i < MISSION_BACKUP_DEPTH; i++)
undo_desc[i].Empty();
}

Expand Down Expand Up @@ -124,7 +124,7 @@ bool CFREDDoc::autoload() {
cf_delete(backup_name, CF_TYPE_MISSIONS);

// Rename Backups. .002 becomes .001, .003 becomes .002, etc.
for (i = 1; i < BACKUP_DEPTH; i++) {
for (i = 1; i < MISSION_BACKUP_DEPTH; i++) {
sprintf(backup_name + len, ".%.3d", i + 1);
sprintf(name + len, ".%.3d", i);
cf_rename(backup_name, name, CF_TYPE_MISSIONS);
Expand Down Expand Up @@ -155,7 +155,7 @@ int CFREDDoc::autosave(char *desc) {
return -1;
}

for (i = BACKUP_DEPTH; i > 1; i--) {
for (i = MISSION_BACKUP_DEPTH; i > 1; i--) {
undo_desc[i] = undo_desc[i - 1];
}

Expand Down Expand Up @@ -535,7 +535,7 @@ void CFREDDoc::OnFileImportFSM() {
if (num_files > 1)
{
create_new_mission();
MessageBox(NULL, "Import complete. Please check the destination folder to verify all missions were imported successfully.", "Status", MB_OK);
Fred_view_wnd->MessageBox("Import complete. Please check the destination folder to verify all missions were imported successfully.", "Status", MB_OK);
}
else if (num_files == 1)
{
Expand Down Expand Up @@ -568,7 +568,8 @@ BOOL CFREDDoc::OnNewDocument() {
return TRUE;
}

BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) {
BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname)
{
if (Briefing_dialog)
Briefing_dialog->icon_select(-1); // clean things up first

Expand All @@ -585,7 +586,58 @@ BOOL CFREDDoc::OnOpenDocument(LPCTSTR pathname) {
strncpy(Mission_filename, filename, len);
Mission_filename[len] = 0;

if (!load_mission(pathname)) {
// first, just grab the info of this mission
if (!parse_main(pathname, MPF_ONLY_MISSION_INFO))
{
*Mission_filename = 0;
return FALSE;
}
SCP_string created = The_mission.created;
CFileLocation res = cf_find_file_location(pathname, CF_TYPE_ANY);
time_t modified = res.m_time;
if (!res.found)
{
UNREACHABLE("Couldn't find path '%s' even though parse_main() succeeded!", pathname);
created = ""; // prevent any backup check from succeeding so we just load the actual specified file
}

// now check all the autosaves
SCP_string backup_name;
CFileLocation backup_res;
for (int i = 1; i <= MISSION_BACKUP_DEPTH; ++i)
{
backup_name = MISSION_BACKUP_NAME;
char extension[5];
sprintf(extension, ".%.3d", i);
backup_name += extension;

backup_res = cf_find_file_location(backup_name.c_str(), CF_TYPE_MISSIONS);
if (backup_res.found && parse_main(backup_res.full_name.c_str(), MPF_ONLY_MISSION_INFO))
{
SCP_string this_created = The_mission.created;
time_t this_modified = backup_res.m_time;

if (created == this_created && this_modified > modified)
break;
}

backup_name.clear();
}

// maybe load from the backup instead
if (!backup_name.empty())
{
SCP_string prompt = "Autosaved file ";
prompt += backup_name;
prompt += " has a file modification time more recent than the specified file. Do you want to load the autosave instead?";
int z = Fred_view_wnd->MessageBox(prompt.c_str(), "Recover from autosave", MB_ICONQUESTION | MB_YESNO);
if (z == IDYES)
pathname = backup_res.full_name.c_str();
}

// now we can actually load either the original path or the backup path
if (!load_mission(pathname))
{
*Mission_filename = 0;
return FALSE;
}
Expand Down Expand Up @@ -816,7 +868,7 @@ Assert((flag == 0) || (flag == 1));
// fp = cfopen(filename, flag ? "wb" : "rb");
// if (!fp)
// MessageBox(NULL, strerror(errno), "File Open Error!", MB_ICONSTOP);
// Fred_view_wnd->MessageBox(strerror(errno), "File Open Error!", MB_ICONSTOP);
// Find highest used object if writing.
if (flag == 1) {
Expand Down
5 changes: 3 additions & 2 deletions fred2/freddoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
*/
#include "MissionSave.h"

#define MISSION_BACKUP_NAME "Backup"
#define MISSION_BACKUP_NAME "Backup"
#define MISSION_BACKUP_DEPTH 9

#define US_WORLD_CHANGED 0x01
#define US_VIEW_CHANGED 0x02
Expand Down Expand Up @@ -145,7 +146,7 @@ class CFREDDoc : public CDocument
virtual void Dump(CDumpContext &dc) const;
#endif

CString undo_desc[BACKUP_DEPTH + 1]; //!< String array of the undo descriptions
CString undo_desc[MISSION_BACKUP_DEPTH + 1]; //!< String array of the undo descriptions

protected:
/**
Expand Down
12 changes: 10 additions & 2 deletions fred2/management.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -815,11 +815,14 @@ void clear_mission(bool fast_reload)
}
}
}
t = CTime::GetCurrentTime();

time_t currentTime;
time(&currentTime);
auto timeinfo = localtime(&currentTime);

strcpy_s(The_mission.name, "Untitled");
The_mission.author = str;
strcpy_s(The_mission.created, t.Format("%x at %X"));
time_to_mission_info_string(timeinfo, The_mission.created, DATE_TIME_LENGTH - 1);
strcpy_s(The_mission.modified, The_mission.created);
strcpy_s(The_mission.notes, "This is a FRED2_OPEN created mission.");
strcpy_s(The_mission.mission_desc, "Put mission description here");
Expand Down Expand Up @@ -2540,3 +2543,8 @@ void update_texture_replacements(const char *old_name, const char *new_name)
strcpy_s(ii->ship_name, new_name);
}
}

void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len)
{
std::strftime(dest, dest_max_len, "%x at %X", src);
}
2 changes: 2 additions & 0 deletions fred2/management.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,6 @@ extern void stuff_special_arrival_anchor_name(char* buf, int iff_index, int rest
extern void stuff_special_arrival_anchor_name(char* buf, int anchor_num, int retail_format);
extern void update_texture_replacements(const char* old_name, const char* new_name);

extern void time_to_mission_info_string(const std::tm* src, char* dest, size_t dest_max_len);

#endif
11 changes: 6 additions & 5 deletions fred2/missionsave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ int CFred_mission_save::autosave_mission_file(char *pathname)
auto len = strlen(pathname);
strcpy_s(backup_name, pathname);
strcpy_s(name2, pathname);
sprintf(backup_name + len, ".%.3d", BACKUP_DEPTH);
sprintf(backup_name + len, ".%.3d", MISSION_BACKUP_DEPTH);
cf_delete(backup_name, CF_TYPE_MISSIONS);
for (i = BACKUP_DEPTH; i > 1; i--) {
for (i = MISSION_BACKUP_DEPTH; i > 1; i--) {
sprintf(backup_name + len, ".%.3d", i - 1);
sprintf(name2 + len, ".%.3d", i);
cf_rename(backup_name, name2, CF_TYPE_MISSIONS);
Expand Down Expand Up @@ -3126,10 +3126,11 @@ int CFred_mission_save::save_mission_info()

void CFred_mission_save::save_mission_internal(const char *pathname)
{
CTime t;
time_t currentTime;
time(&currentTime);
auto timeinfo = localtime(&currentTime);

t = CTime::GetCurrentTime();
strcpy_s(The_mission.modified, t.Format("%x at %X"));
time_to_mission_info_string(timeinfo, The_mission.modified, DATE_TIME_LENGTH - 1);

// Migrate the version!
The_mission.required_fso_version = MISSION_VERSION;
Expand Down
2 changes: 0 additions & 2 deletions fred2/missionsave.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

struct sexp_container;

#define BACKUP_DEPTH 9

/**
* @class CFred_mission_save
*
Expand Down
62 changes: 58 additions & 4 deletions qtfred/src/mission/Editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "object.h"
#include "management.h"
#include "util.h"
#include "FredApplication.h"

namespace {
Expand Down Expand Up @@ -126,6 +127,61 @@ void Editor::update() {
}
}

std::string Editor::maybeUseAutosave(const std::string& filepath)
{
// first, just grab the info of this mission
if (!parse_main(filepath.c_str(), MPF_ONLY_MISSION_INFO))
return filepath;
SCP_string created = The_mission.created;
CFileLocation res = cf_find_file_location(filepath.c_str(), CF_TYPE_ANY);
time_t modified = res.m_time;
if (!res.found)
{
UNREACHABLE("Couldn't find path '%s' even though parse_main() succeeded!", filepath.c_str());
return filepath; // just load the actual specified file
}

// now check all the autosaves
SCP_string backup_name;
CFileLocation backup_res;
for (int i = 1; i <= MISSION_BACKUP_DEPTH; ++i)
{
backup_name = MISSION_BACKUP_NAME;
char extension[5];
sprintf(extension, ".%.3d", i);
backup_name += extension;

backup_res = cf_find_file_location(backup_name.c_str(), CF_TYPE_MISSIONS);
if (backup_res.found && parse_main(backup_res.full_name.c_str(), MPF_ONLY_MISSION_INFO))
{
SCP_string this_created = The_mission.created;
time_t this_modified = backup_res.m_time;

if (created == this_created && this_modified > modified)
break;
}

backup_name.clear();
}

// maybe load from the backup instead
if (!backup_name.empty())
{
SCP_string prompt = "Autosaved file ";
prompt += backup_name;
prompt += " has a file modification time more recent than the specified file. Do you want to load the autosave instead?";

auto z = _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Question,
"Recover from autosave",
prompt.c_str(),
{ DialogButton::Yes, DialogButton::No });
if (z == DialogButton::Yes)
return backup_res.full_name.c_str();
}

return filepath;
}

bool Editor::loadMission(const std::string& mission_name, int flags) {
char name[512], * old_name;
int i, j, k, ob;
Expand Down Expand Up @@ -418,13 +474,11 @@ void Editor::clearMission(bool fast_reload) {

time_t currentTime;
time(&currentTime);
auto tm_info = localtime(&currentTime);
char time_buffer[26];
strftime(time_buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info);
auto timeinfo = localtime(&currentTime);

strcpy_s(The_mission.name, "Untitled");
The_mission.author = userName;
strcpy_s(The_mission.created, time_buffer);
time_to_mission_info_string(timeinfo, The_mission.created, DATE_TIME_LENGTH - 1);
strcpy_s(The_mission.modified, The_mission.created);
strcpy_s(The_mission.notes, "This is a FRED2_OPEN created mission.");
strcpy_s(The_mission.mission_desc, "Put mission description here");
Expand Down
5 changes: 5 additions & 0 deletions qtfred/src/mission/Editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <memory>
#include <stdexcept>

#define MISSION_BACKUP_NAME "Backup"
#define MISSION_BACKUP_DEPTH 9

namespace fso {
namespace fred {

Expand All @@ -36,6 +39,8 @@ class Editor : public QObject {

void createNewMission();

std::string maybeUseAutosave(const std::string& filepath);

/*! Load a mission. */
bool loadMission(const std::string& filepath, int flags = 0);

Expand Down
Loading

0 comments on commit b5351c7

Please sign in to comment.