Additional autosave speed improvement and 1 fix

This changes the autosave XML file to a binary representation
during writing to speed up autosave processing.  A lot of the
time used during autosave is a result of having to convert and
print all of the values to the XML file.

Writing the same information, but in binary format, reduces
all of that to just the bare essentials and the actual write
I/O.

During recovery, the binary file is read and converted to
the real xML representation and processing happens as it
did before.

It is a noticeable difference with very long or many tracks.

The included fix has to do with append recording.

Say you have 3 tracks and you want to append recorded audio
to the middle track.  Sometime later Audacity crashes and
upon recovery, the recorded audio is actually appended to
the third track, not the second one.

This fixes that by adding an "autosaveid" to each track as
it is written to the autosave file.  The same ID is written
to the recording recovery appends to the autosave file.

Then, during recovery, the IDs are matched up and the audio
gets appended to the proper track.

These autosaveid attributes are only present in the autosave
file and not in saved project files.
This commit is contained in:
Leland Lucius 2015-04-17 16:42:30 -05:00
parent 6a84f657c3
commit 3e1fbcd5ec
10 changed files with 683 additions and 90 deletions

View File

@ -3384,7 +3384,7 @@ void AudioIO::FillBuffers()
{
// Append captured samples to the end of the WaveTracks.
// The WaveTracks have their own buffering for efficiency.
XMLStringWriter blockFileLog;
AutoSaveFile blockFileLog;
int numChannels = mCaptureTracks.GetCount();
for( i = 0; (int)i < numChannels; i++ )
@ -3392,7 +3392,7 @@ void AudioIO::FillBuffers()
int avail = commonlyAvail;
sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat();
XMLStringWriter appendLog;
AutoSaveFile appendLog;
if( mFactor == 1.0 )
{
@ -3423,6 +3423,7 @@ void AudioIO::FillBuffers()
if (!appendLog.IsEmpty())
{
blockFileLog.StartTag(wxT("recordingrecovery"));
blockFileLog.WriteAttr(wxT("id"), mCaptureTracks[i]->GetAutoSaveIdent());
blockFileLog.WriteAttr(wxT("channel"), (int)i);
blockFileLog.WriteAttr(wxT("numchannels"), numChannels);
blockFileLog.WriteSubTree(appendLog);

View File

@ -15,6 +15,8 @@
#include <wx/string.h>
#include "AutoRecovery.h"
class AUDACITY_DLL_API AudioIOListener {
public:
AudioIOListener() {}
@ -23,7 +25,7 @@ public:
virtual void OnAudioIORate(int rate) = 0;
virtual void OnAudioIOStartRecording() = 0;
virtual void OnAudioIOStopRecording() = 0;
virtual void OnAudioIONewBlockFiles(const wxString& blockFileLog) = 0;
virtual void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) = 0;
};
#endif

View File

@ -274,13 +274,29 @@ bool RecordingRecoveryHandler::HandleXMLTag(const wxChar *tag,
// We need to find the track and sequence where the blockfile belongs
WaveTrackArray tracks = mProject->GetTracks()->GetWaveTrackArray(false);
int index = tracks.GetCount() - mNumChannels + mChannel;
if (index < 0 || index >= (int)tracks.GetCount())
size_t index;
if (mAutoSaveIdent)
{
for (index = 0; index < tracks.GetCount(); index++)
{
if (tracks[index]->GetAutoSaveIdent() == mAutoSaveIdent)
{
break;
}
}
}
else
{
index = tracks.GetCount() - mNumChannels + mChannel;
}
if (index < 0 || index >= 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();
@ -302,6 +318,8 @@ bool RecordingRecoveryHandler::HandleXMLTag(const wxChar *tag,
} else if (wxStrcmp(tag, wxT("recordingrecovery")) == 0)
{
mAutoSaveIdent = 0;
// loop through attrs, which is a null-terminated list of
// attribute-value pairs
long nValue;
@ -331,6 +349,13 @@ bool RecordingRecoveryHandler::HandleXMLTag(const wxChar *tag,
return false;
mNumChannels = nValue;
}
else if (wxStrcmp(attr, wxT("id")) == 0)
{
if (!XMLValueChecker::IsGoodInt(strValue) || !strValue.ToLong(&nValue) ||
(nValue < 1))
return false;
mAutoSaveIdent = nValue;
}
}
}
@ -345,3 +370,511 @@ XMLTagHandler* RecordingRecoveryHandler::HandleXMLChild(const wxChar *tag)
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 as 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 tell the decoder
// to preserve the active dictionary. The decoder when 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);
short len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.c_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);
short len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.c_str(), len);
}
void AutoSaveFile::Write(const wxString & value)
{
mBuffer.PutC(FT_Raw);
short len = value.Length() * sizeof(wxChar);
mBuffer.Write(&len, sizeof(len));
mBuffer.Write(value.c_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)
{
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.c_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)
{
// Not something we recognize. Could be decoded already. Let the caller
// deal with it.
file.Close();
return true;
}
len = file.Length() - len;
char *buf = new char[len];
if (file.Read(buf, len) != len)
{
delete buf;
file.Close();
return false;
}
wxMemoryInputStream in(buf, len);
file.Close();
// Decode to a temporary file to preserve the orignal.
wxString tempName = fn.CreateTempFileName(fn.GetPath(true));
XMLFileWriter out;
out.Open(tempName, wxT("wb"));
if (!out.IsOpened())
{
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:
{
short 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:
{
short 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:
{
short 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;
}

View File

@ -12,9 +12,15 @@
#define __AUDACITY_AUTORECOVERY__
#include "Project.h"
#include "xml/XMLTagHandler.h"
#include "xml/XMLWriter.h"
#include <wx/debug.h>
#include <wx/dynarray.h>
#include <wx/ffile.h>
#include <wx/hashmap.h>
#include <wx/mstream.h>
//
// Show auto recovery dialog if there are projects to recover. Should be
@ -49,6 +55,65 @@ private:
AudacityProject* mProject;
int mChannel;
int mNumChannels;
int mAutoSaveIdent;
};
///
/// AutoSaveFile
///
// Should be plain ASCII
#define AutoSaveIdent "<?xml autosave>"
WX_DECLARE_STRING_HASH_MAP_WITH_DECL(short, NameMap, class AUDACITY_DLL_API);
WX_DECLARE_HASH_MAP(short, wxString, wxIntegerHash, wxIntegerEqual, IdMap);
WX_DECLARE_OBJARRAY(IdMap, IdMapArray);
class AUDACITY_DLL_API AutoSaveFile : public XMLWriter
{
public:
AutoSaveFile(size_t allocSize = 1024 * 1024);
virtual ~AutoSaveFile();
virtual void StartTag(const wxString & name);
virtual void EndTag(const wxString & name);
virtual void WriteAttr(const wxString & name, const wxString &value);
virtual void WriteAttr(const wxString & name, const wxChar *value);
virtual void WriteAttr(const wxString & name, int value);
virtual void WriteAttr(const wxString & name, bool value);
virtual void WriteAttr(const wxString & name, long value);
virtual void WriteAttr(const wxString & name, long long value);
virtual void WriteAttr(const wxString & name, size_t value);
virtual void WriteAttr(const wxString & name, float value, int digits = -1);
virtual void WriteAttr(const wxString & name, double value, int digits = -1);
virtual void WriteData(const wxString & value);
virtual void WriteSubTree(const AutoSaveFile & value);
virtual void Write(const wxString & data);
virtual bool Write(wxFFile & file) const;
virtual bool Append(wxFFile & file) const;
virtual bool IsEmpty() const;
virtual bool Decode(const wxString & fileName);
private:
void WriteName(const wxString & name);
void CheckSpace(wxMemoryOutputStream & buf);
private:
wxMemoryOutputStream mBuffer;
wxMemoryOutputStream mDict;
NameMap mNames;
IdMap mIds;
IdMapArray mIdStack;
size_t mAllocSize;
};
#endif

View File

@ -2478,13 +2478,6 @@ void AudacityProject::OpenFile(wxString fileName, bool addtohistory)
return;
}
// We want to open projects using wxTextFile, but if it's NOT a project
// file (but actually a WAV file, for example), then wxTextFile will spin
// for a long time searching for line breaks. So, we look for our
// signature at the beginning of the file first:
wxString firstLine = wxT("AudacityProject");
if (!::wxFileExists(fileName)) {
wxMessageBox(_("Could not open file: ") + fileName,
_("Error Opening File"),
@ -2492,6 +2485,11 @@ void AudacityProject::OpenFile(wxString fileName, bool addtohistory)
return;
}
// We want to open projects using wxTextFile, but if it's NOT a project
// file (but actually a WAV file, for example), then wxTextFile will spin
// for a long time searching for line breaks. So, we look for our
// signature at the beginning of the file first:
wxFFile *ff = new wxFFile(fileName, wxT("rb"));
if (!ff->IsOpened()) {
wxMessageBox(_("Could not open file: ") + fileName,
@ -2504,9 +2502,9 @@ void AudacityProject::OpenFile(wxString fileName, bool addtohistory)
wxMessageBox(wxString::Format(_("File may be invalid or corrupted: \n%s"),
(const wxChar*)fileName), _("Error Opening File or Project"),
wxOK | wxCENTRE, this);
ff->Close();
delete ff;
return;
ff->Close();
delete ff;
return;
}
buf[15] = 0;
ff->Close();
@ -2558,31 +2556,13 @@ void AudacityProject::OpenFile(wxString fileName, bool addtohistory)
if (mFileName.Length() >= autoSaveExt.Length() &&
mFileName.Right(autoSaveExt.Length()) == autoSaveExt)
{
// This is an auto-save file, add </project> tag, if necessary
wxFile f(fileName, wxFile::read_write);
if (f.IsOpened())
AutoSaveFile asf;
if (!asf.Decode(fileName))
{
// Read the last 16 bytes of the file and check if they contain
// "</project>" somewhere.
const int bufsize = 16;
char buf[bufsize];
bool seekOk, readOk;
seekOk = f.SeekEnd(-bufsize) != wxInvalidOffset;
if (seekOk)
readOk = (f.Read(buf, bufsize) == bufsize);
else
readOk = false;
if (readOk && !strstr(buf, "</project>"))
{
// End of file does not contain closing </project> tag, so add it
if (f.Seek(0, wxFromEnd) != wxInvalidOffset)
{
strcpy(buf, "</project>\n");
f.Write(buf, strlen(buf));
}
}
f.Close();
wxMessageBox(_("Could not decode file: ") + fileName,
_("Error decoding file"),
wxOK | wxCENTRE, this);
return;
}
}
@ -3187,8 +3167,18 @@ void AudacityProject::WriteXML(XMLWriter &xmlFile)
if (t->GetLinked())
t = iter.Next();
}
else
else if (t->GetKind() == Track::Wave && mAutoSaving)
{
pWaveTrack = (WaveTrack*)t;
pWaveTrack->SetAutoSaveIdent(++ndx);
t->WriteXML(xmlFile);
}
else
{
pWaveTrack = (WaveTrack*)t;
pWaveTrack->SetAutoSaveIdent(0);
t->WriteXML(xmlFile);
}
t = iter.Next();
}
@ -4653,20 +4643,16 @@ void AudacityProject::AutoSave()
try
{
XMLStringWriter buffer(1024 * 1024);
VarSetter<bool> setter(&mAutoSaving, true, false);
AutoSaveFile buffer;
WriteXMLHeader(buffer);
WriteXML(buffer);
XMLFileWriter saveFile;
wxFFile saveFile;
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
saveFile.WriteSubTree(buffer);
// JKC Calling XMLFileWriter::Close will close the <project> scope.
// We certainly don't want to do that, if we're doing recordingrecovery,
// because the recordingrecovery tags need to be inside <project></project>.
// So instead we do not call Close() but CloseWithoutEndingTags().
saveFile.CloseWithoutEndingTags();
buffer.Write(saveFile);
saveFile.Close();
}
catch (XMLFileWriterException* pException)
{
@ -4748,13 +4734,7 @@ void AudacityProject::OnAudioIOStartRecording()
{
// Before recording is started, auto-save the file. The file will have
// empty tracks at the bottom where the recording will be put into
//
// When block files are cached, auto recovery is disabled while recording,
// since no block files are written during recording that could be
// recovered.
//
if (!GetCacheBlockFiles())
AutoSave();
AutoSave();
}
// This is called after recording has stopped and all tracks have flushed.
@ -4785,30 +4765,19 @@ void AudacityProject::OnAudioIOStopRecording()
AutoSave();
}
void AudacityProject::OnAudioIONewBlockFiles(const wxString& blockFileLog)
void AudacityProject::OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog)
{
// New blockfiles have been created, so add them to the auto-save file
if (!GetCacheBlockFiles() &&
!mAutoSaveFileName.IsEmpty())
if (!mAutoSaveFileName.IsEmpty())
{
wxFFile f(mAutoSaveFileName, wxT("at"));
wxFFile f(mAutoSaveFileName, wxT("ab"));
if (!f.IsOpened())
return; // Keep recording going, there's not much we can do here
f.Write(blockFileLog);
blockFileLog.Append(f);
f.Close();
}
}
bool AudacityProject::GetCacheBlockFiles()
{
bool cacheBlockFiles = false;
#ifdef DEPRECATED_AUDIO_CACHE
// See http://bugzilla.audacityteam.org/show_bug.cgi?id=545.
gPrefs->Read(wxT("/Directories/CacheBlockFiles"), &cacheBlockFiles);
#endif
return cacheBlockFiles;
}
void AudacityProject::SetSnapTo(int snap)
{
AS_SetSnapTo(snap);

View File

@ -436,7 +436,7 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
virtual void OnAudioIORate(int rate);
virtual void OnAudioIOStartRecording();
virtual void OnAudioIOStopRecording();
virtual void OnAudioIONewBlockFiles(const wxString& blockFileLog);
virtual void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog);
// Command Handling
bool TryToMakeActionAllowed( wxUint32 & flags, wxUint32 flagsRqd, wxUint32 mask );
@ -464,8 +464,6 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
void AutoSave();
void DeleteCurrentAutoSaveFile();
static bool GetCacheBlockFiles();
public:
bool IsSoloSimple() { return mSoloPref == wxT("Simple"); }
bool IsSoloNone() { return mSoloPref == wxT("None"); }

View File

@ -1482,6 +1482,9 @@ bool WaveTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
else if (!wxStrcmp(attr, wxT("linked")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
SetLinked(nValue != 0);
else if (!wxStrcmp(attr, wxT("autosaveid")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
mAutoSaveIdent = (int) nValue;
} // while
#ifdef EXPERIMENTAL_OUTPUT_DISPLAY
@ -1539,6 +1542,10 @@ XMLTagHandler *WaveTrack::HandleXMLChild(const wxChar *tag)
void WaveTrack::WriteXML(XMLWriter &xmlFile)
{
xmlFile.StartTag(wxT("wavetrack"));
if (mAutoSaveIdent)
{
xmlFile.WriteAttr(wxT("autosaveid"), mAutoSaveIdent);
}
xmlFile.WriteAttr(wxT("name"), mName);
xmlFile.WriteAttr(wxT("channel"), mChannel);
xmlFile.WriteAttr(wxT("linked"), mLinked);
@ -2376,3 +2383,13 @@ void WaveTrack::AddInvalidRegion(sampleCount startSample, sampleCount endSample)
for (WaveClipList::compatibility_iterator it=GetClipIterator(); it; it=it->GetNext())
it->GetData()->AddInvalidRegion(startSample,endSample);
}
int WaveTrack::GetAutoSaveIdent()
{
return mAutoSaveIdent;
}
void WaveTrack::SetAutoSaveIdent(int ident)
{
mAutoSaveIdent = ident;
}

View File

@ -390,6 +390,14 @@ class AUDACITY_DLL_API WaveTrack: public Track {
// Resample track (i.e. all clips in the track)
bool Resample(int rate, ProgressDialog *progress = NULL);
//
// AutoSave related
//
// Retrieve the unique autosave ID
int GetAutoSaveIdent();
// Set the unique autosave ID
void SetAutoSaveIdent(int id);
//
// The following code will eventually become part of a GUIWaveTrack
// and will be taken out of the WaveTrack class:
@ -454,7 +462,7 @@ class AUDACITY_DLL_API WaveTrack: public Track {
wxCriticalSection mFlushCriticalSection;
wxCriticalSection mAppendCriticalSection;
double mLegacyProjectFileOffset;
int mAutoSaveIdent;
};
#endif // __AUDACITY_WAVETRACK__

View File

@ -33,7 +33,6 @@ the general functionality for creating XML in UTF8 encoding.
#include "../Internat.h"
#include "XMLWriter.h"
#include "XMLTagHandler.h"
//table for xml encoding compatibility with expat decoding
//see wxWidgets-2.8.12/src/expat/lib/xmltok_impl.h

View File

@ -10,8 +10,9 @@
#ifndef __AUDACITY_XML_XML_FILE_WRITER__
#define __AUDACITY_XML_XML_FILE_WRITER__
#include <wx/ffile.h>
#include <wx/arrstr.h>
#include <wx/dynarray.h>
#include <wx/ffile.h>
///
/// XMLWriter
@ -23,23 +24,23 @@ class AUDACITY_DLL_API XMLWriter {
XMLWriter();
virtual ~XMLWriter();
void StartTag(const wxString &name);
void EndTag(const wxString &name);
virtual void StartTag(const wxString &name);
virtual void EndTag(const wxString &name);
void WriteAttr(const wxString &name, const wxString &value);
void WriteAttr(const wxString &name, const wxChar *value);
virtual void WriteAttr(const wxString &name, const wxString &value);
virtual void WriteAttr(const wxString &name, const wxChar *value);
void WriteAttr(const wxString &name, int value);
void WriteAttr(const wxString &name, bool value);
void WriteAttr(const wxString &name, long value);
void WriteAttr(const wxString &name, long long value);
void WriteAttr(const wxString &name, size_t value);
void WriteAttr(const wxString &name, float value, int digits = -1);
void WriteAttr(const wxString &name, double value, int digits = -1);
virtual void WriteAttr(const wxString &name, int value);
virtual void WriteAttr(const wxString &name, bool value);
virtual void WriteAttr(const wxString &name, long value);
virtual void WriteAttr(const wxString &name, long long value);
virtual void WriteAttr(const wxString &name, size_t value);
virtual void WriteAttr(const wxString &name, float value, int digits = -1);
virtual void WriteAttr(const wxString &name, double value, int digits = -1);
void WriteData(const wxString &value);
virtual void WriteData(const wxString &value);
void WriteSubTree(const wxString &value);
virtual void WriteSubTree(const wxString &value);
virtual void Write(const wxString &data) = 0;