1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-11 09:03:36 +02:00
Files
audacity/src/AutoRecovery.cpp
Leland Lucius 0e7a063ced Do not alloc the auto recovery dialog on the stack.
Was dumping the allocated window structure and noticed that this
fella never really goes away.  Could be the deleting was still
pending, but it doesn't hurt to alloc/delete it instead.
2015-08-14 16:02:16 -05:00

948 lines
23 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Audacity(R) is copyright (c) 1999-2010 Audacity Team.
License: GPL v2. See License.txt.
AutoRecovery.cpp
*******************************************************************//**
\class AutoRecoveryDialog
\brief The AutoRecoveryDialog prompts the user whether to
recover previous Audacity projects that were closed incorrectly.
*//********************************************************************/
#include "AutoRecovery.h"
#include "Audacity.h"
#include "AudacityApp.h"
#include "FileNames.h"
#include "blockfile/SimpleBlockFile.h"
#include "Sequence.h"
#include "ShuttleGui.h"
#include <wx/wxprec.h>
#include <wx/filefn.h>
#include <wx/dir.h>
#include <wx/dialog.h>
#include <wx/app.h>
#include "WaveTrack.h"
enum {
ID_RECOVER_ALL = 10000,
ID_RECOVER_NONE,
ID_QUIT_AUDACITY,
ID_FILE_LIST
};
class AutoRecoveryDialog : public wxDialog
{
public:
AutoRecoveryDialog(wxWindow *parent);
private:
void PopulateList();
void PopulateOrExchange(ShuttleGui & S);
void OnQuitAudacity(wxCommandEvent &evt);
void OnRecoverNone(wxCommandEvent &evt);
void OnRecoverAll(wxCommandEvent &evt);
wxListCtrl *mFileList;
public:
DECLARE_EVENT_TABLE()
};
AutoRecoveryDialog::AutoRecoveryDialog(wxWindow *parent) :
wxDialog(parent, -1, _("Automatic Crash Recovery"),
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE & (~wxCLOSE_BOX)) // no close box
{
SetName(GetTitle());
ShuttleGui S(this, eIsCreating);
PopulateOrExchange(S);
}
BEGIN_EVENT_TABLE(AutoRecoveryDialog, wxDialog)
EVT_BUTTON(ID_RECOVER_ALL, AutoRecoveryDialog::OnRecoverAll)
EVT_BUTTON(ID_RECOVER_NONE, AutoRecoveryDialog::OnRecoverNone)
EVT_BUTTON(ID_QUIT_AUDACITY, AutoRecoveryDialog::OnQuitAudacity)
END_EVENT_TABLE()
void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui& S)
{
S.SetBorder(5);
S.StartVerticalLay();
{
S.AddVariableText(_("Some projects were not saved properly the last time Audacity was run.\nFortunately, the following projects can be automatically recovered:"), false);
S.StartStatic(_("Recoverable projects"));
{
mFileList = S.Id(ID_FILE_LIST).AddListControlReportMode();
/*i18n-hint: (noun). It's the name of the project to recover.*/
mFileList->InsertColumn(0, _("Name"));
mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE);
PopulateList();
}
S.EndStatic();
S.AddVariableText(_("After recovery, save the project to save the changes to disk."), false);
S.StartHorizontalLay();
{
S.Id(ID_QUIT_AUDACITY).AddButton(_("Quit Audacity"));
S.Id(ID_RECOVER_NONE).AddButton(_("Discard Projects"));
S.Id(ID_RECOVER_ALL).AddButton(_("Recover Projects"));
}
S.EndHorizontalLay();
}
S.EndVerticalLay();
Layout();
Fit();
SetMinSize(GetSize());
// Sometimes it centers on wxGTK and sometimes it doesn't.
// Yielding before centering seems to be a good workaround,
// but will leave to implement on a rainy day.
Center();
}
void AutoRecoveryDialog::PopulateList()
{
mFileList->DeleteAllItems();
wxDir dir(FileNames::AutoSaveDir());
if (!dir.IsOpened())
return;
wxString filename;
int i = 0;
for (bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES);
c; c = dir.GetNext(&filename))
mFileList->InsertItem(i++, wxFileName(filename).GetName());
mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE);
}
void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent & WXUNUSED(event))
{
EndModal(ID_QUIT_AUDACITY);
}
void AutoRecoveryDialog::OnRecoverNone(wxCommandEvent & WXUNUSED(event))
{
int ret = wxMessageBox(
_("Are you sure you want to discard all projects?\n\nChoosing \"Yes\" discards all projects immediately."),
_("Confirm Discard Projects"), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, this);
if (ret == wxYES)
EndModal(ID_RECOVER_NONE);
}
void AutoRecoveryDialog::OnRecoverAll(wxCommandEvent & WXUNUSED(event))
{
EndModal(ID_RECOVER_ALL);
}
////////////////////////////////////////////////////////////////////////////
static bool HaveFilesToRecover()
{
wxDir dir(FileNames::AutoSaveDir());
if (!dir.IsOpened())
{
wxMessageBox(_("Could not enumerate files in auto save directory."),
_("Error"), wxICON_STOP);
return false;
}
wxString filename;
bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES);
return c;
}
static bool RemoveAllAutoSaveFiles()
{
wxArrayString files;
wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files,
wxT("*.autosave"), wxDIR_FILES);
for (unsigned int i = 0; i < files.GetCount(); i++)
{
if (!wxRemoveFile(files[i]))
{
// I don't think this error message is actually useful.
// -dmazzoni
//wxMessageBox(wxT("Could not remove auto save file: " + files[i]),
// _("Error"), wxICON_STOP);
return false;
}
}
return true;
}
static bool RecoverAllProjects(AudacityProject** pproj)
{
wxDir dir(FileNames::AutoSaveDir());
if (!dir.IsOpened())
{
wxMessageBox(_("Could not enumerate files in auto save directory."),
_("Error"), wxICON_STOP);
return false;
}
// Open a project window for each auto save file
wxString filename;
AudacityProject* proj = NULL;
wxArrayString files;
wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files,
wxT("*.autosave"), wxDIR_FILES);
for (unsigned int i = 0; i < files.GetCount(); i++)
{
if (*pproj)
{
// Reuse existing project window
proj = *pproj;
*pproj = NULL;
} else
{
// Create new project window
proj = CreateNewAudacityProject();
}
// Open project. When an auto-save file has been opened successfully,
// the opened auto-save file is automatically deleted and a new one
// is created.
proj->OpenFile(files[i], false);
}
return true;
}
bool ShowAutoRecoveryDialogIfNeeded(AudacityProject** pproj,
bool *didRecoverAnything)
{
if (didRecoverAnything)
*didRecoverAnything = false;
if (HaveFilesToRecover())
{
// Under wxGTK3, the auto recovery dialog will not get
// the focus since the project window hasn't been allowed
// to completely initialize.
//
// Yielding seems to allow the initialization to complete.
//
// Additionally, it also corrects a sizing issue in the dialog
// related to wxWidgets bug:
//
// http://trac.wxwidgets.org/ticket/16440
//
// This must be done before "dlg" is declared.
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI);
AutoRecoveryDialog *dlg = new AutoRecoveryDialog(NULL); //*pproj);
int ret = dlg->ShowModal();
delete dlg;
switch (ret)
{
case ID_RECOVER_NONE:
return RemoveAllAutoSaveFiles();
case ID_RECOVER_ALL:
if (didRecoverAnything)
*didRecoverAnything = true;
return RecoverAllProjects(pproj);
default:
// This includes ID_QUIT_AUDACITY
return false;
}
} else
{
// Nothing to recover, move along
return true;
}
}
////////////////////////////////////////////////////////////////////////////
/// Recording recovery handler
RecordingRecoveryHandler::RecordingRecoveryHandler(AudacityProject* proj)
{
mProject = proj;
mChannel = -1;
mNumChannels = -1;
}
bool RecordingRecoveryHandler::HandleXMLTag(const wxChar *tag,
const wxChar **attrs)
{
if (wxStrcmp(tag, wxT("simpleblockfile")) == 0)
{
// Check if we have a valid channel and numchannels
if (mChannel < 0 || mNumChannels < 0 || mChannel >= mNumChannels)
{
// This should only happen if there is a bug
wxASSERT(false);
return false;
}
// We need to find the track and sequence where the blockfile belongs
WaveTrackArray tracks = mProject->GetTracks()->GetWaveTrackArray(false);
int index;
if (mAutoSaveIdent)
{
for (index = 0; index < (int) tracks.GetCount(); index++)
{
if (tracks[index]->GetAutoSaveIdent() == mAutoSaveIdent)
{
break;
}
}
}
else
{
index = tracks.GetCount() - mNumChannels + mChannel;
}
if (index < 0 || index >= (int) tracks.GetCount())
{
// This should only happen if there is a bug
wxASSERT(false);
return false;
}
WaveTrack* track = tracks.Item(index);
WaveClip* clip = track->NewestOrNewClip();
Sequence* seq = clip->GetSequence();
// Load the blockfile from the XML
BlockFile* blockFile = NULL;
DirManager* dirManager = mProject->GetDirManager();
dirManager->SetLoadingFormat(seq->GetSampleFormat());
dirManager->SetLoadingTarget(&blockFile);
if (!dirManager->HandleXMLTag(tag, attrs) || !blockFile)
{
// This should only happen if there is a bug
wxASSERT(false);
return false;
}
seq->AppendBlockFile(blockFile);
clip->UpdateEnvelopeTrackLen();
} else if (wxStrcmp(tag, wxT("recordingrecovery")) == 0)
{
mAutoSaveIdent = 0;
// loop through attrs, which is a null-terminated list of
// attribute-value pairs
long nValue;
while(*attrs)
{
const wxChar *attr = *attrs++;
const wxChar *value = *attrs++;
if (!value)
break;
const wxString strValue = value;
//this channels value does not correspond to WaveTrack::Left/Right/Mono, but which channel of the recording device
//it came from, and thus we can't use XMLValueChecker::IsValidChannel on it. Rather we compare to the next attribute value.
if (wxStrcmp(attr, wxT("channel")) == 0)
{
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) || nValue < 0)
return false;
mChannel = nValue;
}
else if (wxStrcmp(attr, wxT("numchannels")) == 0)
{
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) ||
(nValue < 1))
return false;
if(mChannel >= nValue )
return false;
mNumChannels = nValue;
}
else if (wxStrcmp(attr, wxT("id")) == 0)
{
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) ||
(nValue < 1))
return false;
mAutoSaveIdent = nValue;
}
}
}
return true;
}
XMLTagHandler* RecordingRecoveryHandler::HandleXMLChild(const wxChar *tag)
{
if (wxStrcmp(tag, wxT("simpleblockfile")) == 0)
return this; // HandleXMLTag also handles <simpleblockfile>
return NULL;
}
///
/// AutoSaveFile class
///
// Simple "binary xml" format used exclusively for autosave files.
//
// It is not intended to transport these files across platform architectures,
// so endianness is not a concern.
//
// It is not intended that the user view or modify the file.
//
// It IS intended that very little work be done during auto save, so numbers
// and strings are written in their native format. They will be converted
// during recovery.
//
// The file has 3 main sections:
//
// ident literal "<?xml autosave>"
// name dictionary dictionary of all names used in the document
// data fields the "encoded" XML document
//
// If a subtree is added, it will be preceeded with FT_Push to tell the decoder
// to preserve the active dictionary. The decoder will then restore the
// dictionary when an FT_Pop is encountered. Nesting is unlimited.
//
// To save space, each name (attribute or element) encountered is stored in
// the name dictionary and replaced with the assigned 2-byte identifier.
//
// All strings are in native unicode format, 2-byte or 4-byte.
//
// All "lengths" are 2-byte signed, so are limited to 32767 bytes long.
enum FieldTypes
{
FT_StartTag, // type, ID, name
FT_EndTag, // type, ID, name
FT_String, // type, ID, name, string length, string
FT_Int, // type, ID, value
FT_Bool, // type, ID, value
FT_Long, // type, ID, value
FT_LongLong, // type, ID, value
FT_SizeT, // type, ID, value
FT_Float, // type, ID, value
FT_Double, // type, ID, value
FT_Data, // type, string length, string
FT_Raw, // type, string length, string
FT_Push, // type only
FT_Pop, // type only
FT_Name // type, name length, name
};
#include <wx/arrimpl.cpp>
WX_DEFINE_OBJARRAY(IdMapArray);
AutoSaveFile::AutoSaveFile(size_t allocSize)
{
mAllocSize = allocSize;
}
AutoSaveFile::~AutoSaveFile()
{
}
void AutoSaveFile::StartTag(const wxString & name)
{
mBuffer.PutC(FT_StartTag);
WriteName(name);
}
void AutoSaveFile::EndTag(const wxString & name)
{
mBuffer.PutC(FT_EndTag);
WriteName(name);
}
void AutoSaveFile::WriteAttr(const wxString & name, const wxChar *value)
{
WriteAttr(name, wxString(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, const wxString & value)
{
mBuffer.PutC(FT_String);
WriteName(name);
int len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.wx_str(), len);
}
void AutoSaveFile::WriteAttr(const wxString & name, int value)
{
mBuffer.PutC(FT_Int);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, bool value)
{
mBuffer.PutC(FT_Bool);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, long value)
{
mBuffer.PutC(FT_Long);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, long long value)
{
mBuffer.PutC(FT_LongLong);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, size_t value)
{
mBuffer.PutC(FT_SizeT);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
}
void AutoSaveFile::WriteAttr(const wxString & name, float value, int digits)
{
mBuffer.PutC(FT_Float);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
mBuffer.Write(&digits, sizeof(digits));
}
void AutoSaveFile::WriteAttr(const wxString & name, double value, int digits)
{
mBuffer.PutC(FT_Double);
WriteName(name);
mBuffer.Write(&value, sizeof(value));
mBuffer.Write(&digits, sizeof(digits));
}
void AutoSaveFile::WriteData(const wxString & value)
{
mBuffer.PutC(FT_Data);
int len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.wx_str(), len);
}
void AutoSaveFile::Write(const wxString & value)
{
mBuffer.PutC(FT_Raw);
int len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.wx_str(), len);
}
void AutoSaveFile::WriteSubTree(const AutoSaveFile & value)
{
mBuffer.PutC(FT_Push);
wxStreamBuffer *buf = value.mDict.GetOutputStreamBuffer();
mBuffer.Write(buf->GetBufferStart(), buf->GetIntPosition());
buf = value.mBuffer.GetOutputStreamBuffer();
mBuffer.Write(buf->GetBufferStart(), buf->GetIntPosition());
mBuffer.PutC(FT_Pop);
}
bool AutoSaveFile::Write(wxFFile & file) const
{
bool success = file.Write(AutoSaveIdent, strlen(AutoSaveIdent)) == strlen(AutoSaveIdent);
if (success)
{
success = Append(file);
}
return success;
}
bool AutoSaveFile::Append(wxFFile & file) const
{
wxStreamBuffer *buf = mDict.GetOutputStreamBuffer();
bool success = file.Write(buf->GetBufferStart(), buf->GetIntPosition()) == buf->GetIntPosition();
if (success)
{
buf = mBuffer.GetOutputStreamBuffer();
success = file.Write(buf->GetBufferStart(), buf->GetIntPosition()) == buf->GetIntPosition();
}
return success;
}
void AutoSaveFile::CheckSpace(wxMemoryOutputStream & os)
{
wxStreamBuffer *buf = os.GetOutputStreamBuffer();
size_t left = buf->GetBytesLeft();
if (left == 0)
{
size_t origPos = buf->GetIntPosition();
char *temp = new char[mAllocSize];
buf->Write(temp, mAllocSize);
delete[] temp;
buf->SetIntPosition(origPos);
}
}
void AutoSaveFile::WriteName(const wxString & name)
{
wxASSERT(name.Length() * sizeof(wxChar) <= SHRT_MAX);
short len = name.Length() * sizeof(wxChar);
short id;
if (mNames.count(name))
{
id = mNames[name];
}
else
{
id = mNames.size();
mNames[name] = id;
CheckSpace(mDict);
mDict.PutC(FT_Name);
mDict.Write(&id, sizeof(id));
mDict.Write(&len, sizeof(len));
mDict.Write(name.wx_str(), len);
}
CheckSpace(mBuffer);
mBuffer.Write(&id, sizeof(id));
}
bool AutoSaveFile::IsEmpty() const
{
return mBuffer.GetLength() == 0;
}
bool AutoSaveFile::Decode(const wxString & fileName)
{
char ident[sizeof(AutoSaveIdent)];
size_t len = strlen(AutoSaveIdent);
wxFileName fn(fileName);
wxFFile file;
if (!file.Open(fn.GetFullPath(), wxT("rb")))
{
return false;
}
if (file.Read(&ident, len) != len || strncmp(ident, AutoSaveIdent, len) != 0)
{
// It could be that the file has already been decoded or that it is one
// from 2.1.0 or earlier. In the latter case, we need to ensure the
// closing </project> tag is preset.
// Close the file so we can reopen it in read/write mode
file.Close();
// Add </project> tag, if necessary
if (!file.Open(fn.GetFullPath(), wxT("r+b")))
{
// Really shouldn't happen, but let the caller deal with it
return false;
}
// Read the last 16 bytes of the file and check if they contain
// "</project>" somewhere.
const int bufsize = 16;
char buf[bufsize + 1];
if (file.SeekEnd(-bufsize))
{
if (file.Read(buf, bufsize) == bufsize)
{
buf[bufsize] = 0;
if (strstr(buf, "</project>") == 0)
{
// End of file does not contain closing </project> tag, so add it
if (file.Seek(0, wxFromEnd))
{
strcpy(buf, "</project>\n");
file.Write(buf, strlen(buf));
}
}
}
}
file.Close();
return true;
}
len = file.Length() - len;
char *buf = new char[len];
if (file.Read(buf, len) != len)
{
delete[] buf;
return false;
}
wxMemoryInputStream in(buf, len);
file.Close();
// Decode to a temporary file to preserve the orignal.
wxString tempName = fn.CreateTempFileName(fn.GetFullPath());
bool opened = false;
XMLFileWriter out;
try
{
out.Open(tempName, wxT("wb"));
opened = out.IsOpened();
}
catch (XMLFileWriterException* pException)
{
delete pException;
}
if (!opened)
{
delete[] buf;
wxRemoveFile(tempName);
return false;
}
mIds.clear();
while (!in.Eof() && !out.Error())
{
short id;
switch (in.GetC())
{
case FT_Push:
{
mIdStack.Add(mIds);
mIds.clear();
}
break;
case FT_Pop:
{
mIds = mIdStack[mIdStack.GetCount() - 1];
mIdStack.RemoveAt(mIdStack.GetCount() - 1);
}
break;
case FT_Name:
{
short len;
in.Read(&id, sizeof(id));
in.Read(&len, sizeof(len));
wxChar *name = new wxChar[len / sizeof(wxChar)];
in.Read(name, len);
mIds[id] = wxString(name, len / sizeof(wxChar));
delete[] name;
}
break;
case FT_StartTag:
{
in.Read(&id, sizeof(id));
out.StartTag(mIds[id]);
}
break;
case FT_EndTag:
{
in.Read(&id, sizeof(id));
out.EndTag(mIds[id]);
}
break;
case FT_String:
{
int len;
in.Read(&id, sizeof(id));
in.Read(&len, sizeof(len));
wxChar *val = new wxChar[len / sizeof(wxChar)];
in.Read(val, len);
out.WriteAttr(mIds[id], wxString(val, len / sizeof(wxChar)));
delete[] val;
}
break;
case FT_Float:
{
float val;
int dig;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
in.Read(&dig, sizeof(dig));
out.WriteAttr(mIds[id], val, dig);
}
break;
case FT_Double:
{
double val;
int dig;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
in.Read(&dig, sizeof(dig));
out.WriteAttr(mIds[id], val, dig);
}
break;
case FT_Int:
{
int val;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
out.WriteAttr(mIds[id], val);
}
break;
case FT_Bool:
{
bool val;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
out.WriteAttr(mIds[id], val);
}
break;
case FT_Long:
{
long val;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
out.WriteAttr(mIds[id], val);
}
break;
case FT_LongLong:
{
long long val;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
out.WriteAttr(mIds[id], val);
}
break;
case FT_SizeT:
{
size_t val;
in.Read(&id, sizeof(id));
in.Read(&val, sizeof(val));
out.WriteAttr(mIds[id], val);
}
break;
case FT_Data:
{
int len;
in.Read(&len, sizeof(len));
wxChar *val = new wxChar[len / sizeof(wxChar)];
in.Read(val, len);
out.WriteData(wxString(val, len / sizeof(wxChar)));
delete[] val;
}
break;
case FT_Raw:
{
int len;
in.Read(&len, sizeof(len));
wxChar *val = new wxChar[len / sizeof(wxChar)];
in.Read(val, len);
out.Write(wxString(val, len / sizeof(wxChar)));
delete[] val;
}
break;
default:
wxASSERT(true);
break;
}
}
delete[] buf;
bool error = out.Error();
out.Close();
// Bail if decoding failed.
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;
}