mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-07 15:22:34 +02:00
No ellipses in title bar of file open/import dialogs Auto Recovery Discard dialogs say only recoverable projects are discarded Capitalised button in Dependency dialogue per MS guidelines.
941 lines
24 KiB
C++
941 lines
24 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 "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 final : public wxDialogWrapper
|
|
{
|
|
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) :
|
|
wxDialogWrapper(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, wxDialogWrapper)
|
|
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 recoverable projects?\n\nChoosing \"Yes\" discards all recoverable 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;
|
|
|
|
wxArrayString files;
|
|
wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files,
|
|
wxT("*.autosave"), wxDIR_FILES);
|
|
|
|
for (unsigned int i = 0; i < files.GetCount(); i++)
|
|
{
|
|
AudacityProject* proj{};
|
|
if (*pproj)
|
|
{
|
|
// Reuse existing project window
|
|
proj = *pproj;
|
|
*pproj = NULL;
|
|
}
|
|
|
|
// 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.
|
|
AudacityProject::OpenProject( proj, 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);
|
|
|
|
int ret = AutoRecoveryDialog{nullptr}.ShowModal();
|
|
|
|
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;
|
|
}
|
|
|
|
int RecordingRecoveryHandler::FindTrack() const
|
|
{
|
|
WaveTrackArray tracks = mProject->GetTracks()->GetWaveTrackArray(false);
|
|
int index;
|
|
if (mAutoSaveIdent)
|
|
{
|
|
for (index = 0; index < (int)tracks.size(); index++)
|
|
{
|
|
if (tracks[index]->GetAutoSaveIdent() == mAutoSaveIdent)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
index = tracks.size() - mNumChannels + mChannel;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
WaveTrackArray tracks = mProject->GetTracks()->GetWaveTrackArray(false);
|
|
int index = FindTrack();
|
|
// We need to find the track and sequence where the blockfile belongs
|
|
|
|
if (index < 0 || index >= (int)tracks.size())
|
|
{
|
|
// This should only happen if there is a bug
|
|
wxASSERT(false);
|
|
return false;
|
|
}
|
|
|
|
WaveTrack* track = tracks[index];
|
|
WaveClip* clip = track->NewestOrNewClip();
|
|
Sequence* seq = clip->GetSequence();
|
|
|
|
// Load the blockfile from the XML
|
|
const auto &dirManager = mProject->GetDirManager();
|
|
dirManager->SetLoadingFormat(seq->GetSampleFormat());
|
|
|
|
BlockArray array;
|
|
array.resize(1);
|
|
dirManager->SetLoadingTarget(&array, 0);
|
|
auto &blockFile = array[0].f;
|
|
|
|
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;
|
|
}
|
|
|
|
void RecordingRecoveryHandler::HandleXMLEndTag(const wxChar *tag)
|
|
{
|
|
if (wxStrcmp(tag, wxT("simpleblockfile")) == 0)
|
|
// Still in inner looop
|
|
return;
|
|
|
|
WaveTrackArray tracks = mProject->GetTracks()->GetWaveTrackArray(false);
|
|
int index = FindTrack();
|
|
// We need to find the track and sequence where the blockfile belongs
|
|
|
|
if (index < 0 || index >= (int)tracks.size()) {
|
|
// This should only happen if there is a bug
|
|
wxASSERT(false);
|
|
}
|
|
else {
|
|
WaveTrack* track = tracks[index];
|
|
WaveClip* clip = track->NewestOrNewClip();
|
|
Sequence* seq = clip->GetSequence();
|
|
|
|
seq->ConsistencyCheck(wxT("RecordingRecoveryHandler::HandleXMLEndTag"));
|
|
}
|
|
}
|
|
|
|
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();
|
|
ArrayOf<char> temp{ mAllocSize };
|
|
buf->Write(temp.get(), mAllocSize);
|
|
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);
|
|
|
|
const wxFileName fn(fileName);
|
|
const wxString fnPath{fn.GetFullPath()};
|
|
wxFFile file;
|
|
|
|
if (!file.Open(fnPath, 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(fnPath, 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];
|
|
|
|
// FIXME: TRAP_ERR AutoSaveFile::Decode reports OK even when wxFFile errors.
|
|
// Might be incompletely written file, but not clear that is OK to be
|
|
// silent about.
|
|
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;
|
|
using Chars = ArrayOf < char >;
|
|
using WxChars = ArrayOf < wxChar >;
|
|
Chars buf{ len };
|
|
if (file.Read(buf.get(), len) != len)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxMemoryInputStream in(buf.get(), len);
|
|
|
|
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") };
|
|
|
|
IdMap mIds;
|
|
IdMapArray mIdStack;
|
|
|
|
mIds.clear();
|
|
|
|
while ( !in.Eof() )
|
|
{
|
|
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));
|
|
WxChars name{ len / sizeof(wxChar) };
|
|
in.Read(name.get(), len);
|
|
|
|
mIds[id] = wxString(name.get(), len / sizeof(wxChar));
|
|
}
|
|
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));
|
|
WxChars val{ len / sizeof(wxChar) };
|
|
in.Read(val.get(), len);
|
|
|
|
out.WriteAttr(mIds[id], wxString(val.get(), len / sizeof(wxChar)));
|
|
}
|
|
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));
|
|
WxChars val{ len / sizeof(wxChar) };
|
|
in.Read(val.get(), len);
|
|
|
|
out.WriteData(wxString(val.get(), len / sizeof(wxChar)));
|
|
}
|
|
break;
|
|
|
|
case FT_Raw:
|
|
{
|
|
int len;
|
|
|
|
in.Read(&len, sizeof(len));
|
|
WxChars val{ len / sizeof(wxChar) };
|
|
in.Read(val.get(), len);
|
|
|
|
out.Write(wxString(val.get(), len / sizeof(wxChar)));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
wxASSERT(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
out.Commit();
|
|
|
|
return true;
|
|
} );
|
|
}
|