Skip to content

Commit

Permalink
[New] Autosave: When autosaving to a user-specified folder, there is …
Browse files Browse the repository at this point in the history
…now the possibility to automatically delete autosaves older than x days (default 30).

[New] Autosave: Old autosaves are now moved to the recycling bin by default instead of being deleted permanently. Can be turned off in settings dialog.

git-svn-id: https://source.openmpt.org/svn/openmpt/trunk/OpenMPT@22336 56274372-70c3-4bfc-bfc3-4c3a0b034d27
  • Loading branch information
sagamusix committed Nov 28, 2024
1 parent 3a9411d commit df05b3f
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 162 deletions.
99 changes: 63 additions & 36 deletions mptrack/AutoSaver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ OPENMPT_NAMESPACE_BEGIN


CAutoSaver::CAutoSaver()
: m_lastSave(timeGetTime())
: m_lastSave{timeGetTime()}
{
}

Expand All @@ -53,32 +53,38 @@ uint32 CAutoSaver::GetHistoryDepth() const
return TrackerSettings::Instance().AutosaveHistoryDepth;
}

uint32 CAutoSaver::GetSaveInterval() const
std::chrono::minutes CAutoSaver::GetSaveInterval() const
{
return TrackerSettings::Instance().AutosaveIntervalMinutes;
return std::chrono::minutes{TrackerSettings::Instance().AutosaveIntervalMinutes.Get()};
}


bool CAutoSaver::DoSave(DWORD curTime)
std::chrono::days CAutoSaver::GetRetentionTime() const
{
return std::chrono::days{TrackerSettings::Instance().AutosaveRetentionTimeDays.Get()};
}


bool CAutoSaver::DoSave()
{
// Do nothing if we are already saving, or if time to save has not been reached yet.
if(m_saveInProgress || !CheckTimer(curTime))
if(m_saveInProgress || !CheckTimer(timeGetTime()))
return true;

bool success = true, clearStatus = false;
m_saveInProgress = true;

theApp.BeginWaitCursor(); // Display hour glass
theApp.BeginWaitCursor(); // Display hour glass

for(auto &modDoc : theApp.GetOpenDocuments())
{
if(modDoc->ModifiedSinceLastAutosave())
{
clearStatus = true;
static_cast<CMainFrame *>(theApp.GetMainWnd())->SetHelpText(MPT_CFORMAT("Auto-saving {}...")(modDoc->GetPathNameMpt().GetFilename()));
CMainFrame::GetMainFrame()->SetHelpText(MPT_CFORMAT("Auto-saving {}...")(modDoc->GetPathNameMpt().GetFilename()));
if(SaveSingleFile(*modDoc))
{
CleanUpBackups(*modDoc);
CleanUpAutosaves(*modDoc);
} else
{
TrackerSettings::Instance().AutosaveEnabled = false;
Expand All @@ -87,21 +93,22 @@ bool CAutoSaver::DoSave(DWORD curTime)
}
}
}
CleanUpAutosaves();

m_lastSave = timeGetTime();
theApp.EndWaitCursor(); // End display hour glass
m_saveInProgress = false;

if(clearStatus)
static_cast<CMainFrame *>(theApp.GetMainWnd())->SetHelpText(_T(""));
CMainFrame::GetMainFrame()->SetHelpText(_T(""));

return success;
}


bool CAutoSaver::CheckTimer(DWORD curTime) const
{
return (curTime - m_lastSave) >= GetSaveIntervalMilliseconds();
return std::chrono::milliseconds{curTime - m_lastSave} >= GetSaveInterval();
}


Expand Down Expand Up @@ -149,53 +156,73 @@ mpt::PathString CAutoSaver::BuildFileName(const CModDoc &modDoc) const

bool CAutoSaver::SaveSingleFile(CModDoc &modDoc)
{
// We do not call CModDoc::DoSave as this populates the Recent Files
// list with backups... hence we have duplicated code.. :(
CSoundFile &sndFile = modDoc.GetSoundFile();

mpt::PathString fileName = BuildFileName(modDoc);

// We are actually not going to show the log for autosaved files.
ScopedLogCapturer logcapturer(modDoc, _T(""), nullptr, false);

bool success = false;
mpt::ofstream f(fileName, std::ios::binary);
if(f)
{
switch(modDoc.GetSoundFile().GetBestSaveFormat())
{
case MOD_TYPE_MOD: success = sndFile.SaveMod(f); break;
case MOD_TYPE_S3M: success = sndFile.SaveS3M(f); break;
case MOD_TYPE_XM: success = sndFile.SaveXM(f); break;
case MOD_TYPE_IT: success = sndFile.SaveIT(f, GetUseOriginalPath() ? fileName : mpt::PathString{}); break;
case MOD_TYPE_MPT: success = sndFile.SaveIT(f, GetUseOriginalPath() ? fileName : mpt::PathString{}); break;
default:
// nothing
break;
}
}

return success;
return modDoc.SaveFile(BuildFileName(modDoc), GetUseOriginalPath());
}


void CAutoSaver::CleanUpBackups(const CModDoc &modDoc) const
void CAutoSaver::CleanUpAutosaves(const CModDoc &modDoc) const
{
// Find all autosave files for this document, and delete the oldest ones if there are more than the user wants.
std::vector<mpt::PathString> foundfiles;
FolderScanner scanner(GetBasePath(modDoc, false), FolderScanner::kOnlyFiles, GetBaseName(modDoc) + P_(".AutoSave.*"));
FolderScanner scanner(GetBasePath(modDoc, false), FolderScanner::kOnlyFiles, GetBaseName(modDoc) + P_(".AutoSave.*.*.*"));
mpt::PathString fileName;
while(scanner.Next(fileName))
{
foundfiles.push_back(std::move(fileName));
}
std::sort(foundfiles.begin(), foundfiles.end());
size_t filesToDelete = std::max(static_cast<size_t>(GetHistoryDepth()), foundfiles.size()) - GetHistoryDepth();
const bool deletePermanently = TrackerSettings::Instance().AutosaveDeletePermanently;
for(size_t i = 0; i < filesToDelete; i++)
{
DeleteFile(foundfiles[i].AsNative().c_str());
DeleteAutosave(foundfiles[i], deletePermanently);
}
}


// Find all autosave files in the autosave folder and delete all of those older than X days
void CAutoSaver::CleanUpAutosaves() const
{
if(GetUseOriginalPath() || !GetRetentionTime().count())
return;
auto path = GetPath();
if(!mpt::native_fs{}.is_directory(path))
return;
const std::chrono::seconds maxAge = GetRetentionTime();
const bool deletePermanently = TrackerSettings::Instance().AutosaveDeletePermanently;
FILETIME sysFileTime;
GetSystemTimeAsFileTime(&sysFileTime);
const int64 currentTime = static_cast<int64>(mpt::bit_cast<ULARGE_INTEGER>(sysFileTime).QuadPart);
FolderScanner scanner(std::move(path), FolderScanner::kOnlyFiles, P_("*.AutoSave.*.*.*"));
mpt::PathString fileName;
WIN32_FIND_DATA fileInfo;
while(scanner.Next(fileName, &fileInfo))
{
const auto fileTime = static_cast<int64>(mpt::bit_cast<ULARGE_INTEGER>(fileInfo.ftLastWriteTime).QuadPart);
const auto timeDiff = std::chrono::seconds{(currentTime - fileTime) / 10000000};
if(timeDiff >= maxAge)
DeleteAutosave(fileName, deletePermanently);
}
}


void CAutoSaver::DeleteAutosave(const mpt::PathString &fileName, bool deletePermanently)
{
// Create double-null-terminated path list
// Note: We could supply multiple filenames here. However, if the files in total are too big for the recycling bin,
// this will cause all of them to be deleted permanently. If we supply them one by one, at least some of them
// will go to the recycling bin until it is full.
mpt::winstring path = fileName.AsNative() + _T('\0');
SHFILEOPSTRUCT fos{};
fos.hwnd = CMainFrame::GetMainFrame()->m_hWnd;
fos.wFunc = FO_DELETE;
fos.pFrom = path.c_str();
fos.fFlags = (deletePermanently ? 0 : FOF_ALLOWUNDO) | FOF_NO_UI;
SHFileOperation(&fos);
}

OPENMPT_NAMESPACE_END
15 changes: 7 additions & 8 deletions mptrack/AutoSaver.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,27 @@ class CAutoSaver
public:
CAutoSaver();

bool DoSave(DWORD curTime);
bool DoSave();

bool IsEnabled() const;
bool GetUseOriginalPath() const;
mpt::PathString GetPath() const;
uint32 GetHistoryDepth() const;
uint32 GetSaveInterval() const;
uint32 GetSaveIntervalMilliseconds() const
{
return Clamp(GetSaveInterval(), 0u, (1u << 30) / 60u / 1000u) * 60 * 1000;
}
std::chrono::minutes GetSaveInterval() const;
std::chrono::days GetRetentionTime() const;

private:
bool SaveSingleFile(CModDoc &modDoc);
mpt::PathString GetBasePath(const CModDoc &modDoc, bool createPath) const;
mpt::PathString GetBaseName(const CModDoc &modDoc) const;
mpt::PathString BuildFileName(const CModDoc &modDoc) const;
void CleanUpBackups(const CModDoc &modDoc) const;
void CleanUpAutosaves(const CModDoc &modDoc) const;
void CleanUpAutosaves() const;
bool CheckTimer(DWORD curTime) const;
static void DeleteAutosave(const mpt::PathString &fileName, bool deletePermanently);

DWORD m_lastSave = 0;
//Flag to prevent autosave from starting new saving if previous is still in progress.
// Flag to prevent autosave from starting new saving if previous is still in progress.
bool m_saveInProgress = false;

};
Expand Down
30 changes: 11 additions & 19 deletions mptrack/FolderScanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,37 @@

OPENMPT_NAMESPACE_BEGIN

FolderScanner::FolderScanner(const mpt::PathString &path, FlagSet<ScanType> type, mpt::PathString filter)
: m_paths(1, path)
, m_filter(std::move(filter))
, m_hFind(INVALID_HANDLE_VALUE)
, m_type(type)
FolderScanner::FolderScanner(mpt::PathString path, FlagSet<ScanType> type, mpt::PathString filter)
: m_paths{1, std::move(path)}
, m_filter{std::move(filter)}
, m_type{type}
{
MemsetZero(m_wfd);
}


FolderScanner::~FolderScanner()
{
FindClose(m_hFind);
if(m_hFind != INVALID_HANDLE_VALUE)
FindClose(m_hFind);
}

#if MPT_COMPILER_MSVC
// silence static analyzer false positive for FindFirstFile
#pragma warning(push)
#pragma warning(disable:6387) // 'HANDLE' could be '0'
#endif // MPT_COMPILER_MSVC
bool FolderScanner::Next(mpt::PathString &file)
bool FolderScanner::Next(mpt::PathString &file, WIN32_FIND_DATA *fileInfo)
{
bool found = false;
do
{
if(m_hFind == INVALID_HANDLE_VALUE)
{
if(m_paths.empty())
{
return false;
}

m_currentPath = m_paths.back();
m_currentPath = m_paths.back().WithTrailingSlash();
m_paths.pop_back();
m_currentPath = m_currentPath.WithTrailingSlash();
m_hFind = FindFirstFile(mpt::support_long_path((m_currentPath + m_filter).AsNative()).c_str(), &m_wfd);
}

Expand All @@ -62,29 +58,25 @@ bool FolderScanner::Next(mpt::PathString &file)
{
if(_tcscmp(m_wfd.cFileName, _T("..")) && _tcscmp(m_wfd.cFileName, _T(".")))
{
// Add sub directory
if(m_type[kFindInSubDirectories])
{
// Add sub directory
m_paths.push_back(file);
}
if(m_type[kOnlyDirectories])
{
found = true;
}
}
} else if(m_type[kOnlyFiles])
{
found = true;
}
if(found && fileInfo)
*fileInfo = m_wfd;
} while((nextFile = FindNextFile(m_hFind, &m_wfd)) != FALSE && !found);
}
if(nextFile == FALSE)
{
// Done with this directory, advance to next
if(m_hFind != INVALID_HANDLE_VALUE)
{
FindClose(m_hFind);
}
m_hFind = INVALID_HANDLE_VALUE;
}
} while(!found);
Expand Down
21 changes: 10 additions & 11 deletions mptrack/FolderScanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,19 @@ class FolderScanner
kFindInSubDirectories = 0x04,
};

protected:
std::vector<mpt::PathString> m_paths;
mpt::PathString m_currentPath;
mpt::PathString m_filter;
HANDLE m_hFind;
WIN32_FIND_DATA m_wfd;
FlagSet<ScanType> m_type;

public:
FolderScanner(const mpt::PathString &path, FlagSet<ScanType> type, mpt::PathString filter = MPT_PATHSTRING("*.*"));
FolderScanner(mpt::PathString path, FlagSet<ScanType> type, mpt::PathString filter = P_("*.*"));
~FolderScanner();

// Return one file or directory at a time in parameter file. Returns true if a file was found (file parameter is valid), false if no more files can be found (file parameter is not touched).
bool Next(mpt::PathString &file);
bool Next(mpt::PathString &file, WIN32_FIND_DATA *fileInfo = nullptr);

protected:
std::vector<mpt::PathString> m_paths;
mpt::PathString m_currentPath;
const mpt::PathString m_filter;
HANDLE m_hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA m_wfd{};
const FlagSet<ScanType> m_type;
};

MPT_DECLARE_ENUM(FolderScanner::ScanType)
Expand Down
9 changes: 3 additions & 6 deletions mptrack/MainFrm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2216,17 +2216,14 @@ void CMainFrame::OnTimerGUI()
{
m_dwTimeSec = time;
m_nAvgMixChn = m_nMixChn;
OnUpdateTime(NULL);
OnUpdateTime(nullptr);
}
}

// Idle Time Check
DWORD curTime = timeGetTime();

if(m_AutoSaver.IsEnabled())
{
bool success = m_AutoSaver.DoSave(curTime);
if (!success) // autosave failure; bring up options.
bool success = m_AutoSaver.DoSave();
if(!success) // autosave failure; bring up options.
{
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_PATHS;
OnViewOptions();
Expand Down
Loading

0 comments on commit df05b3f

Please sign in to comment.