mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-02 17:09:26 +02:00
Strong exception safety in all uses of XMLFileWriter...
... Strong, meaning that the file at the specified path is created or modified only if all write operations complete without exceptions, barring one very unlikely possibility that a final file rename fails, but even in that case the output is successfully written to some path. This commit does not add throws, but changes the type thrown to a subclass of AudacityException, so that GuardedCall will cause the user to see an error dialog in all cases. Duplicated logic for making temporary files and backups is now all in one place, the class XMLWriter. There may be more new GuardedCalls than necessary -- the catch-all for the event loop, AudacityApp::OnExceptionInMainLoop, might be trusted instead in some cases -- but they are sufficient.
This commit is contained in:
parent
b81cdee7e3
commit
3bb04245c5
@ -738,243 +738,207 @@ bool AutoSaveFile::Decode(const wxString & fileName)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLFileWriter out;
|
|
||||||
wxString tempName;
|
|
||||||
len = file.Length() - len;
|
len = file.Length() - len;
|
||||||
|
using Chars = ArrayOf < char >;
|
||||||
|
using WxChars = ArrayOf < wxChar >;
|
||||||
|
Chars buf{ len };
|
||||||
|
if (file.Read(buf.get(), len) != len)
|
||||||
{
|
{
|
||||||
using Chars = ArrayOf < char >;
|
return false;
|
||||||
using WxChars = ArrayOf < wxChar >;
|
}
|
||||||
Chars buf{ len };
|
|
||||||
|
|
||||||
if (file.Read(buf.get(), len) != len)
|
wxMemoryInputStream in(buf.get(), len);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxMemoryInputStream in(buf.get(), len);
|
file.Close();
|
||||||
|
|
||||||
file.Close();
|
// JKC: ANSWER-ME: Is the try catch actually doing anything?
|
||||||
|
// If it is useful, why are we not using it everywhere?
|
||||||
|
// If it isn't useful, why are we doing it here?
|
||||||
|
// PRL: Yes, now we are doing GuardedCall everywhere that XMLFileWriter is
|
||||||
|
// used.
|
||||||
|
return GuardedCall< bool >( [&] {
|
||||||
|
XMLFileWriter out{ fileName, _("Error Decoding File") };
|
||||||
|
|
||||||
// Decode to a temporary file to preserve the original.
|
IdMap mIds;
|
||||||
tempName = fn.CreateTempFileName(fnPath);
|
IdMapArray mIdStack;
|
||||||
bool opened = false;
|
|
||||||
|
|
||||||
// JKC: ANSWER-ME: Is the try catch actually doing anything?
|
|
||||||
// If it is useful, why are we not using it everywhere?
|
|
||||||
// If it isn't useful, why are we doing it here?
|
|
||||||
try
|
|
||||||
{
|
|
||||||
out.Open(tempName, wxT("wb"));
|
|
||||||
opened = out.IsOpened();
|
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opened)
|
|
||||||
{
|
|
||||||
wxRemoveFile(tempName);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mIds.clear();
|
mIds.clear();
|
||||||
|
|
||||||
while (!in.Eof() && !out.Error())
|
while ( !in.Eof() )
|
||||||
{
|
{
|
||||||
short id;
|
short id;
|
||||||
|
|
||||||
switch (in.GetC())
|
switch (in.GetC())
|
||||||
{
|
{
|
||||||
case FT_Push:
|
case FT_Push:
|
||||||
{
|
{
|
||||||
mIdStack.Add(mIds);
|
mIdStack.Add(mIds);
|
||||||
mIds.clear();
|
mIds.clear();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Pop:
|
case FT_Pop:
|
||||||
{
|
{
|
||||||
mIds = mIdStack[mIdStack.GetCount() - 1];
|
mIds = mIdStack[mIdStack.GetCount() - 1];
|
||||||
mIdStack.RemoveAt(mIdStack.GetCount() - 1);
|
mIdStack.RemoveAt(mIdStack.GetCount() - 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Name:
|
case FT_Name:
|
||||||
{
|
{
|
||||||
short len;
|
short len;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&len, sizeof(len));
|
in.Read(&len, sizeof(len));
|
||||||
WxChars name{ len / sizeof(wxChar) };
|
WxChars name{ len / sizeof(wxChar) };
|
||||||
in.Read(name.get(), len);
|
in.Read(name.get(), len);
|
||||||
|
|
||||||
mIds[id] = wxString(name.get(), len / sizeof(wxChar));
|
mIds[id] = wxString(name.get(), len / sizeof(wxChar));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_StartTag:
|
case FT_StartTag:
|
||||||
{
|
{
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
|
|
||||||
out.StartTag(mIds[id]);
|
out.StartTag(mIds[id]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_EndTag:
|
case FT_EndTag:
|
||||||
{
|
{
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
|
|
||||||
out.EndTag(mIds[id]);
|
out.EndTag(mIds[id]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_String:
|
case FT_String:
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&len, sizeof(len));
|
in.Read(&len, sizeof(len));
|
||||||
WxChars val{ len / sizeof(wxChar) };
|
WxChars val{ len / sizeof(wxChar) };
|
||||||
in.Read(val.get(), len);
|
in.Read(val.get(), len);
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], wxString(val.get(), len / sizeof(wxChar)));
|
out.WriteAttr(mIds[id], wxString(val.get(), len / sizeof(wxChar)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Float:
|
case FT_Float:
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
int dig;
|
int dig;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
in.Read(&dig, sizeof(dig));
|
in.Read(&dig, sizeof(dig));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val, dig);
|
out.WriteAttr(mIds[id], val, dig);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Double:
|
case FT_Double:
|
||||||
{
|
{
|
||||||
double val;
|
double val;
|
||||||
int dig;
|
int dig;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
in.Read(&dig, sizeof(dig));
|
in.Read(&dig, sizeof(dig));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val, dig);
|
out.WriteAttr(mIds[id], val, dig);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Int:
|
case FT_Int:
|
||||||
{
|
{
|
||||||
int val;
|
int val;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val);
|
out.WriteAttr(mIds[id], val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Bool:
|
case FT_Bool:
|
||||||
{
|
{
|
||||||
bool val;
|
bool val;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val);
|
out.WriteAttr(mIds[id], val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Long:
|
case FT_Long:
|
||||||
{
|
{
|
||||||
long val;
|
long val;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val);
|
out.WriteAttr(mIds[id], val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_LongLong:
|
case FT_LongLong:
|
||||||
{
|
{
|
||||||
long long val;
|
long long val;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val);
|
out.WriteAttr(mIds[id], val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_SizeT:
|
case FT_SizeT:
|
||||||
{
|
{
|
||||||
size_t val;
|
size_t val;
|
||||||
|
|
||||||
in.Read(&id, sizeof(id));
|
in.Read(&id, sizeof(id));
|
||||||
in.Read(&val, sizeof(val));
|
in.Read(&val, sizeof(val));
|
||||||
|
|
||||||
out.WriteAttr(mIds[id], val);
|
out.WriteAttr(mIds[id], val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Data:
|
case FT_Data:
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
in.Read(&len, sizeof(len));
|
in.Read(&len, sizeof(len));
|
||||||
WxChars val{ len / sizeof(wxChar) };
|
WxChars val{ len / sizeof(wxChar) };
|
||||||
in.Read(val.get(), len);
|
in.Read(val.get(), len);
|
||||||
|
|
||||||
out.WriteData(wxString(val.get(), len / sizeof(wxChar)));
|
out.WriteData(wxString(val.get(), len / sizeof(wxChar)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FT_Raw:
|
case FT_Raw:
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
in.Read(&len, sizeof(len));
|
in.Read(&len, sizeof(len));
|
||||||
WxChars val{ len / sizeof(wxChar) };
|
WxChars val{ len / sizeof(wxChar) };
|
||||||
in.Read(val.get(), len);
|
in.Read(val.get(), len);
|
||||||
|
|
||||||
out.Write(wxString(val.get(), len / sizeof(wxChar)));
|
out.Write(wxString(val.get(), len / sizeof(wxChar)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
wxASSERT(true);
|
wxASSERT(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool error = out.Error();
|
out.Commit();
|
||||||
|
|
||||||
out.Close();
|
|
||||||
|
|
||||||
// Bail if decoding failed.
|
return true;
|
||||||
if (error)
|
} );
|
||||||
{
|
|
||||||
// File successfully decoded
|
|
||||||
wxRemoveFile(tempName);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoding was successful, so remove the original file and replace with decoded one.
|
|
||||||
if (wxRemoveFile(fileName))
|
|
||||||
{
|
|
||||||
if (!wxRenameFile(tempName, fileName))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,6 @@ private:
|
|||||||
wxMemoryOutputStream mBuffer;
|
wxMemoryOutputStream mBuffer;
|
||||||
wxMemoryOutputStream mDict;
|
wxMemoryOutputStream mDict;
|
||||||
NameMap mNames;
|
NameMap mNames;
|
||||||
IdMap mIds;
|
|
||||||
IdMapArray mIdStack;
|
|
||||||
size_t mAllocSize;
|
size_t mAllocSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,44 +44,6 @@ On failure the old version is put back in place.
|
|||||||
#include "Legacy.h"
|
#include "Legacy.h"
|
||||||
#include "xml/XMLWriter.h"
|
#include "xml/XMLWriter.h"
|
||||||
|
|
||||||
class AutoRollbackRenamer {
|
|
||||||
public:
|
|
||||||
AutoRollbackRenamer(wxString oldName, wxString newName) {
|
|
||||||
mOldName = oldName;
|
|
||||||
mNewName = newName;
|
|
||||||
mRenameSucceeded = ::wxRenameFile(mOldName, mNewName);
|
|
||||||
mFinished = false;
|
|
||||||
mNewFile = NULL;
|
|
||||||
}
|
|
||||||
~AutoRollbackRenamer()
|
|
||||||
{
|
|
||||||
if (mNewFile)
|
|
||||||
fclose(mNewFile);
|
|
||||||
|
|
||||||
if (mRenameSucceeded && !mFinished) {
|
|
||||||
::wxRemoveFile(mOldName);
|
|
||||||
::wxRenameFile(mNewName, mOldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool RenameSucceeded()
|
|
||||||
{
|
|
||||||
return mRenameSucceeded;
|
|
||||||
}
|
|
||||||
void Finished()
|
|
||||||
{
|
|
||||||
mFinished = true;
|
|
||||||
}
|
|
||||||
void SetNewFile(FILE *f)
|
|
||||||
{
|
|
||||||
mNewFile = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxString mOldName, mNewName;
|
|
||||||
bool mRenameSucceeded;
|
|
||||||
bool mFinished;
|
|
||||||
FILE *mNewFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
@ -291,42 +253,15 @@ static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
|||||||
bool ConvertLegacyProjectFile(const wxFileName &filename)
|
bool ConvertLegacyProjectFile(const wxFileName &filename)
|
||||||
{
|
{
|
||||||
wxTextFile f;
|
wxTextFile f;
|
||||||
XMLFileWriter xmlFile;
|
|
||||||
int index = 0;
|
|
||||||
wxString backupName;
|
|
||||||
|
|
||||||
do {
|
const wxString name = filename.GetFullPath();
|
||||||
index++;
|
f.Open( name );
|
||||||
fflush(stdout);
|
|
||||||
backupName = filename.GetPath() + wxFILE_SEP_PATH + filename.GetName() +
|
|
||||||
wxT("_bak") + wxString::Format(wxT("%d"), index) + wxT(".") + filename.GetExt();
|
|
||||||
} while(::wxFileExists(backupName));
|
|
||||||
|
|
||||||
// This will move the original file out of the way, but
|
|
||||||
// move it back if we exit from this function early.
|
|
||||||
AutoRollbackRenamer renamer(filename.GetFullPath(), backupName);
|
|
||||||
if (!renamer.RenameSucceeded())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
f.Open(backupName);
|
|
||||||
if (!f.IsOpened())
|
if (!f.IsOpened())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
wxString name = filename.GetFullPath();
|
return GuardedCall< bool >( [&] {
|
||||||
|
XMLFileWriter xmlFile{ name, _("Error Converting Legacy Project File") };
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
xmlFile.Open(name, wxT("wb"));
|
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renamer.SetNewFile(xmlFile.fp());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
xmlFile.Write(wxT("<?xml version=\"1.0\"?>\n"));
|
xmlFile.Write(wxT("<?xml version=\"1.0\"?>\n"));
|
||||||
|
|
||||||
wxString label;
|
wxString label;
|
||||||
@ -360,19 +295,15 @@ bool ConvertLegacyProjectFile(const wxFileName &filename)
|
|||||||
label = f.GetNextLine();
|
label = f.GetNextLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close original before Commit() tries to overwrite it.
|
||||||
|
f.Close();
|
||||||
|
|
||||||
xmlFile.EndTag(wxT("audacityproject"));
|
xmlFile.EndTag(wxT("audacityproject"));
|
||||||
xmlFile.Close();
|
xmlFile.Commit();
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
// Error writing XML file (e.g. disk full)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renamer.Finished();
|
::wxMessageBox(wxString::Format(_("Converted a 1.0 project file to the new format.\nThe old file has been saved as '%s'"), xmlFile.GetBackupName().c_str()),
|
||||||
|
_("Opening Audacity Project"));
|
||||||
|
|
||||||
::wxMessageBox(wxString::Format(_("Converted a 1.0 project file to the new format.\nThe old file has been saved as '%s'"), backupName.c_str()),
|
return true;
|
||||||
_("Opening Audacity Project"));
|
} );
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -3803,37 +3803,21 @@ bool AudacityProject::Save(bool overwrite /* = true */ ,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the AUP file.
|
auto success = GuardedCall< bool >( [&] {
|
||||||
XMLFileWriter saveFile;
|
// Write the AUP file.
|
||||||
|
XMLFileWriter saveFile{ mFileName, _("Error Saving Project") };
|
||||||
try
|
|
||||||
{
|
|
||||||
saveFile.Open(mFileName, wxT("wb"));
|
|
||||||
|
|
||||||
WriteXMLHeader(saveFile);
|
WriteXMLHeader(saveFile);
|
||||||
WriteXML(saveFile);
|
WriteXML(saveFile);
|
||||||
mStrOtherNamesArray.Clear();
|
mStrOtherNamesArray.Clear();
|
||||||
|
|
||||||
saveFile.Close();
|
saveFile.Commit();
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
mFileName.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Project"), wxICON_ERROR);
|
|
||||||
|
|
||||||
// When XMLWriter throws an exception, it tries to close it before,
|
return true;
|
||||||
// so we can at least try to DELETE the incomplete file and move the
|
} );
|
||||||
// backup file over.
|
|
||||||
if (safetyFileName != wxT(""))
|
|
||||||
{
|
|
||||||
wxRemove(mFileName);
|
|
||||||
wxRename(safetyFileName, mFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!success)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (bWantSaveCompressed)
|
if (bWantSaveCompressed)
|
||||||
mWantSaveCompressed = false; // Don't want this mode for AudacityProject::WriteXML() any more.
|
mWantSaveCompressed = false; // Don't want this mode for AudacityProject::WriteXML() any more.
|
||||||
@ -5103,7 +5087,9 @@ void AudacityProject::AutoSave()
|
|||||||
wxString fn = wxFileName(FileNames::AutoSaveDir(),
|
wxString fn = wxFileName(FileNames::AutoSaveDir(),
|
||||||
projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath();
|
projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath();
|
||||||
|
|
||||||
try
|
// PRL: I found a try-catch and rewrote it,
|
||||||
|
// but this guard is unnecessary because AutoSaveFile does not throw
|
||||||
|
bool success = GuardedCall< bool >( [&]
|
||||||
{
|
{
|
||||||
VarSetter<bool> setter(&mAutoSaving, true, false);
|
VarSetter<bool> setter(&mAutoSaving, true, false);
|
||||||
|
|
||||||
@ -5114,18 +5100,11 @@ void AudacityProject::AutoSave()
|
|||||||
|
|
||||||
wxFFile saveFile;
|
wxFFile saveFile;
|
||||||
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
|
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
|
||||||
buffer.Write(saveFile);
|
return buffer.Write(saveFile);
|
||||||
saveFile.Close();
|
} );
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
(fn + wxT(".tmp")).c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Writing Autosave File"), wxICON_ERROR, this);
|
|
||||||
|
|
||||||
|
if (!success)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have a NEW auto-save file, DELETE the old one
|
// Now that we have a NEW auto-save file, DELETE the old one
|
||||||
DeleteCurrentAutoSaveFile();
|
DeleteCurrentAutoSaveFile();
|
||||||
|
43
src/Tags.cpp
43
src/Tags.cpp
@ -1219,12 +1219,9 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create/Open the file
|
GuardedCall< void >( [&] {
|
||||||
XMLFileWriter writer;
|
// Create/Open the file
|
||||||
|
XMLFileWriter writer{ fn, _("Error Saving Tags File") };
|
||||||
try
|
|
||||||
{
|
|
||||||
writer.Open(fn, wxT("wb"));
|
|
||||||
|
|
||||||
// Remember title and track in case they're read only
|
// Remember title and track in case they're read only
|
||||||
wxString title = mLocal.GetTag(TAG_TITLE);
|
wxString title = mLocal.GetTag(TAG_TITLE);
|
||||||
@ -1240,29 +1237,23 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||||||
mLocal.SetTag(TAG_TRACK, wxEmptyString);
|
mLocal.SetTag(TAG_TRACK, wxEmptyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto cleanup = finally( [&] {
|
||||||
|
// Restore title
|
||||||
|
if (!mEditTitle) {
|
||||||
|
mLocal.SetTag(TAG_TITLE, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore track
|
||||||
|
if (!mEditTrack) {
|
||||||
|
mLocal.SetTag(TAG_TRACK, track);
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
// Write the metadata
|
// Write the metadata
|
||||||
mLocal.WriteXML(writer);
|
mLocal.WriteXML(writer);
|
||||||
|
|
||||||
// Restore title
|
writer.Commit();
|
||||||
if (!mEditTitle) {
|
} );
|
||||||
mLocal.SetTag(TAG_TITLE, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore track
|
|
||||||
if (!mEditTrack) {
|
|
||||||
mLocal.SetTag(TAG_TRACK, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the file
|
|
||||||
writer.Close();
|
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
fn.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Tags File"), wxICON_ERROR, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagsEditor::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
|
void TagsEditor::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
|
@ -3580,6 +3580,7 @@ void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
|
|||||||
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
||||||
{
|
{
|
||||||
// may throw
|
// may throw
|
||||||
|
// exceptions are handled in AudacityApp::OnExceptionInMainLoop
|
||||||
mClient->ExportPresets();
|
mClient->ExportPresets();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1601,27 +1601,16 @@ void EffectEqualization::SaveCurves(const wxString &fileName)
|
|||||||
else
|
else
|
||||||
fn = fileName;
|
fn = fileName;
|
||||||
|
|
||||||
// Create/Open the file
|
GuardedCall< void >( [&] {
|
||||||
XMLFileWriter eqFile;
|
// Create/Open the file
|
||||||
const wxString fullPath{ fn.GetFullPath() };
|
const wxString fullPath{ fn.GetFullPath() };
|
||||||
|
XMLFileWriter eqFile{ fullPath, _("Error Saving Equalization Curves") };
|
||||||
try
|
|
||||||
{
|
|
||||||
eqFile.Open( fullPath, wxT("wb") );
|
|
||||||
|
|
||||||
// Write the curves
|
// Write the curves
|
||||||
WriteXML( eqFile );
|
WriteXML( eqFile );
|
||||||
|
|
||||||
// Close the file
|
eqFile.Commit();
|
||||||
eqFile.Close();
|
} );
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
fullPath.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Equalization Curves"), wxICON_ERROR, mUIParent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -3562,10 +3562,7 @@ void VSTEffect::SaveFXProgram(wxMemoryBuffer & buf, int index)
|
|||||||
void VSTEffect::SaveXML(const wxFileName & fn)
|
void VSTEffect::SaveXML(const wxFileName & fn)
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
XMLFileWriter xmlFile;
|
XMLFileWriter xmlFile{ fn.GetFullPath(), _("Error Saving Effect Presets") };
|
||||||
|
|
||||||
// Create/Open the file
|
|
||||||
xmlFile.Open(fn.GetFullPath(), wxT("wb"));
|
|
||||||
|
|
||||||
xmlFile.StartTag(wxT("vstprogrampersistence"));
|
xmlFile.StartTag(wxT("vstprogrampersistence"));
|
||||||
xmlFile.WriteAttr(wxT("version"), wxT("2"));
|
xmlFile.WriteAttr(wxT("version"), wxT("2"));
|
||||||
@ -3616,10 +3613,7 @@ void VSTEffect::SaveXML(const wxFileName & fn)
|
|||||||
|
|
||||||
xmlFile.EndTag(wxT("vstprogrampersistence"));
|
xmlFile.EndTag(wxT("vstprogrampersistence"));
|
||||||
|
|
||||||
// Close the file
|
xmlFile.Commit();
|
||||||
xmlFile.Close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VSTEffect::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
bool VSTEffect::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||||
|
@ -491,12 +491,15 @@ FFmpegPresets::FFmpegPresets()
|
|||||||
|
|
||||||
FFmpegPresets::~FFmpegPresets()
|
FFmpegPresets::~FFmpegPresets()
|
||||||
{
|
{
|
||||||
XMLFileWriter writer;
|
// We're in a destructor! Don't let exceptions out!
|
||||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
GuardedCall< void >( [&] {
|
||||||
wxFileName xmlFileName(FileNames::DataDir(), wxT("ffmpeg_presets.xml"));
|
wxFileName xmlFileName{ FileNames::DataDir(), wxT("ffmpeg_presets.xml") };
|
||||||
writer.Open(xmlFileName.GetFullPath(),wxT("wb"));
|
XMLFileWriter writer{
|
||||||
WriteXMLHeader(writer);
|
xmlFileName.GetFullPath(), _("Error Saving FFmpeg Presets") };
|
||||||
WriteXML(writer);
|
WriteXMLHeader(writer);
|
||||||
|
WriteXML(writer);
|
||||||
|
writer.Commit();
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegPresets::ImportPresets(wxString &filename)
|
void FFmpegPresets::ImportPresets(wxString &filename)
|
||||||
@ -515,11 +518,12 @@ void FFmpegPresets::ImportPresets(wxString &filename)
|
|||||||
|
|
||||||
void FFmpegPresets::ExportPresets(wxString &filename)
|
void FFmpegPresets::ExportPresets(wxString &filename)
|
||||||
{
|
{
|
||||||
XMLFileWriter writer;
|
GuardedCall< void >( [&] {
|
||||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
XMLFileWriter writer{ filename, _("Error Saving FFmpeg Presets") };
|
||||||
writer.Open(filename,wxT("wb"));
|
WriteXMLHeader(writer);
|
||||||
WriteXMLHeader(writer);
|
WriteXML(writer);
|
||||||
WriteXML(writer);
|
writer.Commit();
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegPresets::GetPresetList(wxArrayString &list)
|
void FFmpegPresets::GetPresetList(wxArrayString &list)
|
||||||
|
@ -376,20 +376,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||||||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||||
gPrefs->Flush();
|
gPrefs->Flush();
|
||||||
|
|
||||||
XMLFileWriter prefFile;
|
GuardedCall< void >( [&] {
|
||||||
|
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||||
try
|
|
||||||
{
|
|
||||||
prefFile.Open(file, wxT("wb"));
|
|
||||||
mManager->WriteXML(prefFile);
|
mManager->WriteXML(prefFile);
|
||||||
prefFile.Close();
|
prefFile.Commit();
|
||||||
}
|
} );
|
||||||
catch (const XMLFileWriterException &)
|
|
||||||
{
|
|
||||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
|
||||||
_("Error Exporting Keyboard Shortcuts"),
|
|
||||||
wxOK | wxCENTRE, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
@ -957,20 +948,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||||||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||||
gPrefs->Flush();
|
gPrefs->Flush();
|
||||||
|
|
||||||
XMLFileWriter prefFile;
|
GuardedCall< void >( [&] {
|
||||||
|
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||||
try
|
|
||||||
{
|
|
||||||
prefFile.Open(file, wxT("wb"));
|
|
||||||
mManager->WriteXML(prefFile);
|
mManager->WriteXML(prefFile);
|
||||||
prefFile.Close();
|
prefFile.Commit();
|
||||||
}
|
} );
|
||||||
catch (const XMLFileWriterException &)
|
|
||||||
{
|
|
||||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
|
||||||
_("Error Exporting Keyboard Shortcuts"),
|
|
||||||
wxOK | wxCENTRE, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
|
@ -265,58 +265,107 @@ wxString XMLWriter::XMLEsc(const wxString & s)
|
|||||||
///
|
///
|
||||||
/// XMLFileWriter class
|
/// XMLFileWriter class
|
||||||
///
|
///
|
||||||
XMLFileWriter::XMLFileWriter()
|
XMLFileWriter::XMLFileWriter
|
||||||
|
( const wxString &outputPath, const wxString &caption, bool keepBackup )
|
||||||
|
: mOutputPath{ outputPath }
|
||||||
|
, mCaption{ caption }
|
||||||
|
, mKeepBackup{ keepBackup }
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
}
|
auto tempPath = wxFileName::CreateTempFileName( outputPath );
|
||||||
|
if (!wxFFile::Open(tempPath, wxT("wb")) || !IsOpened())
|
||||||
|
ThrowException( tempPath, mCaption );
|
||||||
|
|
||||||
XMLFileWriter::~XMLFileWriter()
|
if (mKeepBackup) {
|
||||||
{
|
int index = 0;
|
||||||
if (IsOpened()) {
|
wxString backupName;
|
||||||
Close();
|
|
||||||
|
do {
|
||||||
|
wxFileName outputFn{ mOutputPath };
|
||||||
|
index++;
|
||||||
|
mBackupName =
|
||||||
|
outputFn.GetPath() + wxFILE_SEP_PATH +
|
||||||
|
outputFn.GetName() + wxT("_bak") +
|
||||||
|
wxString::Format(wxT("%d"), index) + wxT(".") +
|
||||||
|
outputFn.GetExt();
|
||||||
|
} while( ::wxFileExists( mBackupName ) );
|
||||||
|
|
||||||
|
// Open the backup file to be sure we can write it and reserve it
|
||||||
|
// until committing
|
||||||
|
if (! mBackupFile.Open( mBackupName, "wb" ) || ! mBackupFile.IsOpened() )
|
||||||
|
ThrowException( mBackupName, mCaption );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Open(const wxString &name, const wxString &mode)
|
|
||||||
|
XMLFileWriter::~XMLFileWriter()
|
||||||
{
|
{
|
||||||
if (!wxFFile::Open(name, mode))
|
// Don't let a destructor throw!
|
||||||
throw XMLFileWriterException(_("Error Opening File"));
|
GuardedCall< void >( [&] {
|
||||||
|
if (IsOpened()) {
|
||||||
|
// Was not committed
|
||||||
|
auto fileName = GetName();
|
||||||
|
CloseWithoutEndingTags();
|
||||||
|
::wxRemoveFile( fileName );
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Close()
|
void XMLFileWriter::Commit()
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
while (mTagstack.GetCount()) {
|
while (mTagstack.GetCount()) {
|
||||||
EndTag(mTagstack[0]);
|
EndTag(mTagstack[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto tempPath = GetName();
|
||||||
CloseWithoutEndingTags();
|
CloseWithoutEndingTags();
|
||||||
|
|
||||||
|
if (mKeepBackup) {
|
||||||
|
if (! mBackupFile.Close() ||
|
||||||
|
! wxRenameFile( mOutputPath, mBackupName ) )
|
||||||
|
ThrowException( mBackupName, mCaption );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( ! wxRemoveFile( mOutputPath ) )
|
||||||
|
ThrowException( mOutputPath, mCaption );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have vacated the file at the output path and are committed.
|
||||||
|
// But not completely finished with steps of the commit operation.
|
||||||
|
// If this step fails, we haven't lost the successfully written data,
|
||||||
|
// but just failed to put it in the right place.
|
||||||
|
if (! wxRenameFile( tempPath, mOutputPath ) )
|
||||||
|
throw FileException{
|
||||||
|
FileException::Cause::Rename, tempPath, mCaption, mOutputPath
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::CloseWithoutEndingTags()
|
void XMLFileWriter::CloseWithoutEndingTags()
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
// Before closing, we first flush it, because if Flush() fails because of a
|
// Before closing, we first flush it, because if Flush() fails because of a
|
||||||
// "disk full" condition, we can still at least try to close the file.
|
// "disk full" condition, we can still at least try to close the file.
|
||||||
if (!wxFFile::Flush())
|
if (!wxFFile::Flush())
|
||||||
{
|
{
|
||||||
wxFFile::Close();
|
wxFFile::Close();
|
||||||
/* i18n-hint: 'flushing' means writing any remaining queued up changes
|
ThrowException( GetName(), mCaption );
|
||||||
* to disk that have not yet been written.*/
|
|
||||||
throw XMLFileWriterException(_("Error Flushing File"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this should never fail if flushing worked.
|
// Note that this should never fail if flushing worked.
|
||||||
if (!wxFFile::Close())
|
if (!wxFFile::Close())
|
||||||
throw XMLFileWriterException(_("Error Closing File"));
|
ThrowException( GetName(), mCaption );
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Write(const wxString &data)
|
void XMLFileWriter::Write(const wxString &data)
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
if (!wxFFile::Write(data, wxConvUTF8))
|
if (!wxFFile::Write(data, wxConvUTF8) || Error())
|
||||||
{
|
{
|
||||||
// When writing fails, we try to close the file before throwing the
|
// When writing fails, we try to close the file before throwing the
|
||||||
// exception, so it can at least be deleted.
|
// exception, so it can at least be deleted.
|
||||||
wxFFile::Close();
|
wxFFile::Close();
|
||||||
throw XMLFileWriterException(_("Error Writing to File"));
|
ThrowException( GetName(), mCaption );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include <wx/dynarray.h>
|
#include <wx/dynarray.h>
|
||||||
#include <wx/ffile.h>
|
#include <wx/ffile.h>
|
||||||
|
|
||||||
|
#include "../FileException.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// XMLWriter
|
/// XMLWriter
|
||||||
///
|
///
|
||||||
@ -60,41 +62,55 @@ class AUDACITY_DLL_API XMLWriter /* not final */ {
|
|||||||
///
|
///
|
||||||
/// XMLFileWriter
|
/// XMLFileWriter
|
||||||
///
|
///
|
||||||
class AUDACITY_DLL_API XMLFileWriter final : public wxFFile, public XMLWriter {
|
|
||||||
|
/// This writes to a provisional file, and replaces the previously existing
|
||||||
|
/// contents by a file rename in Commit() only after all writes succeed.
|
||||||
|
/// The original contents may also be retained at a backup path name, as
|
||||||
|
/// directed by the optional constructor argument.
|
||||||
|
/// If it is destroyed before Commit(), then the provisional file is removed.
|
||||||
|
/// If the construction and all operations are inside a GuardedCall or event
|
||||||
|
/// handler, then the default delayed handler action in case of exceptions will
|
||||||
|
/// notify the user of problems.
|
||||||
|
class AUDACITY_DLL_API XMLFileWriter final : private wxFFile, public XMLWriter {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
XMLFileWriter();
|
/// The caption is for message boxes to show in case of errors.
|
||||||
|
/// Might throw.
|
||||||
|
XMLFileWriter
|
||||||
|
( const wxString &outputPath, const wxString &caption,
|
||||||
|
bool keepBackup = false );
|
||||||
|
|
||||||
virtual ~XMLFileWriter();
|
virtual ~XMLFileWriter();
|
||||||
|
|
||||||
/// Open the file. Might throw XMLFileWriterException.
|
/// Close all tags and then close the file.
|
||||||
void Open(const wxString &name, const wxString &mode);
|
/// Might throw. If not, then create
|
||||||
|
/// or modify the file at the output path.
|
||||||
|
void Commit();
|
||||||
|
|
||||||
/// Close file. Might throw XMLFileWriterException.
|
/// Write to file. Might throw.
|
||||||
void Close();
|
|
||||||
|
|
||||||
/// Close file without automatically ending tags.
|
|
||||||
/// Might throw XMLFileWriterException.
|
|
||||||
void CloseWithoutEndingTags(); // for auto-save files
|
|
||||||
|
|
||||||
/// Write to file. Might throw XMLFileWriterException.
|
|
||||||
void Write(const wxString &data) override;
|
void Write(const wxString &data) override;
|
||||||
|
|
||||||
|
wxString GetBackupName() const { return mBackupName; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
};
|
void ThrowException(
|
||||||
|
const wxFileName &fileName, const wxString &caption)
|
||||||
|
{
|
||||||
|
throw FileException{ FileException::Cause::Write, fileName, caption };
|
||||||
|
}
|
||||||
|
|
||||||
///
|
/// Close file without automatically ending tags.
|
||||||
/// Exception thrown by various XMLFileWriter methods
|
/// Might throw.
|
||||||
///
|
void CloseWithoutEndingTags(); // for auto-save files
|
||||||
class XMLFileWriterException
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
XMLFileWriterException(const wxString& message) { mMessage = message; }
|
|
||||||
wxString GetMessage() const { return mMessage; }
|
|
||||||
|
|
||||||
protected:
|
const wxString mOutputPath;
|
||||||
wxString mMessage;
|
const wxString mCaption;
|
||||||
|
wxString mBackupName;
|
||||||
|
const bool mKeepBackup;
|
||||||
|
|
||||||
|
wxFFile mBackupFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user