diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 31a3abdf9..2da58b896 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -75,7 +75,6 @@ It handles initialization and termination by subclassing wxApp. #include "Benchmark.h" #include "Clipboard.h" #include "CrashReport.h" -#include "DirManager.h" #include "commands/CommandHandler.h" #include "commands/AppCommandEvent.h" #include "widgets/ASlider.h" @@ -83,7 +82,6 @@ It handles initialization and termination by subclassing wxApp. //#include "LangChoice.h" #include "Languages.h" #include "Menus.h" -#include "MissingAliasFileDialog.h" #include "PluginManager.h" #include "Project.h" #include "ProjectAudioIO.h" @@ -104,7 +102,6 @@ It handles initialization and termination by subclassing wxApp. #include "AutoRecoveryDialog.h" #include "SplashDialog.h" #include "FFT.h" -#include "BlockFile.h" #include "widgets/AudacityMessageBox.h" #include "prefs/DirectoriesPrefs.h" #include "prefs/GUIPrefs.h" @@ -854,7 +851,7 @@ void AudacityApp::OnMRUClear(wxCommandEvent& WXUNUSED(event)) FileHistory::Global().Clear(); } -//vvv Basically, anything from Recent Files is treated as a .aup, until proven otherwise, +//vvv Basically, anything from Recent Files is treated as a .aup3, until proven otherwise, // then it tries to Import(). Very questionable handling, imo. // Better, for example, to check the file type early on. void AudacityApp::OnMRUFile(wxCommandEvent& event) { @@ -914,44 +911,6 @@ void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event)) } } } - - // Check if a warning for missing aliased files should be displayed - if (MissingAliasFilesDialog::ShouldShow()) { - // find which project owns the blockfile - // note: there may be more than 1, but just go with the first one. - //size_t numProjects = AllProjects{}.size(); - auto marked = MissingAliasFilesDialog::Marked(); - auto offendingProject = marked.second; - wxString missingFileName = marked.first; - - // if there are no projects open, don't show the warning (user has closed it) - if (offendingProject) { - auto &window = GetProjectFrame( *offendingProject ); - window.Iconize(false); - window.Raise(); - - auto errorMessage = XO( -"One or more external audio files could not be found.\n\ -It is possible they were moved, deleted, or the drive they \ -were on was unmounted.\n\ -Silence is being substituted for the affected audio.\n\ -The first detected missing file is:\n\ -%s\n\ -There may be additional missing files.\n\ -Choose Help > Diagnostics > Check Dependencies to view a list of \ -locations of the missing files.").Format( missingFileName ) ; - - // if an old dialog exists, raise it if it is - if ( auto dialog = MissingAliasFilesDialog::Find( *offendingProject ) ) - dialog->Raise(); - else { - MissingAliasFilesDialog::Show(offendingProject.get(), XO("Files Missing"), - errorMessage, wxT(""), true); - } - } - // Only show this warning once per event (playback/menu item/etc). - MissingAliasFilesDialog::SetShouldShow(false); - } } #if defined(__WXMSW__) @@ -1344,21 +1303,6 @@ bool AudacityApp::OnInit() Sequence::SetMaxDiskBlockSize(lval); } - wxString fileName; - if (parser->Found(wxT("d"), &fileName)) - { - AutoSaveFile asf; - if (asf.Decode(fileName)) - { - wxPrintf(_("File decoded successfully\n")); - } - else - { - wxPrintf( AutoSaveFile::FailureMessage( fileName ) .Translation() ); //Debug()? - } - exit(1); - } - // BG: Create a temporary window to set as the top window wxImage logoimage((const char **)AudacityLogoWithName_xpm); logoimage.Rescale(logoimage.GetWidth() / 2, logoimage.GetHeight() / 2); @@ -1493,8 +1437,6 @@ bool AudacityApp::OnInit() bool didRecoverAnything = false; if (!ShowAutoRecoveryDialogIfNeeded(&project, &didRecoverAnything)) { - // Important: Prevent deleting any temporary files! - DirManager::SetDontDeleteTempFiles(); QuitAudacity(true); } @@ -1505,7 +1447,7 @@ bool AudacityApp::OnInit() { if (parser->Found(wxT("t"))) { - RunBenchmark( nullptr, ProjectSettings::Get( *project ) ); + RunBenchmark( nullptr, *project); QuitAudacity(true); } @@ -1689,7 +1631,6 @@ bool AudacityApp::InitTempDir() #endif bool bSuccess = gPrefs->Write(wxT("/Directories/TempDir"), temp) && gPrefs->Flush(); - DirManager::SetTempDir(temp); // Make sure the temp dir isn't locked by another process. if (!CreateSingleInstanceChecker(temp)) @@ -1998,10 +1939,6 @@ std::unique_ptr AudacityApp::ParseCommandLine() parser->AddOption(wxT("b"), wxT("blocksize"), _("set max disk block size in bytes"), wxCMD_LINE_VAL_NUMBER); - /*i18n-hint: This decodes an autosave file */ - parser->AddOption(wxT("d"), wxT("decode"), _("decode an autosave file"), - wxCMD_LINE_VAL_STRING); - /*i18n-hint: This displays a list of available options */ parser->AddSwitch(wxT("h"), wxT("help"), _("this help message"), wxCMD_LINE_OPTION_HELP); @@ -2217,107 +2154,154 @@ void AudacityApp::OnMenuExit(wxCommandEvent & event) #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__) void AudacityApp::AssociateFileTypes() { - wxRegKey associateFileTypes; - associateFileTypes.SetName(wxT("HKCR\\.AUP")); - bool bKeyExists = associateFileTypes.Exists(); - if (!bKeyExists) { - // Not at HKEY_CLASSES_ROOT. Try HKEY_CURRENT_USER. - associateFileTypes.SetName(wxT("HKCU\\Software\\Classes\\.AUP")); - bKeyExists = associateFileTypes.Exists(); + // Check pref in case user has already decided against it. + bool bWantAssociateFiles = true; + if (gPrefs->Read(wxT("/WantAssociateFiles"), &bWantAssociateFiles) && + !bWantAssociateFiles) + { + // User has already decided against it + return; } - if (!bKeyExists) { - // File types are not currently associated. - // Check pref in case user has already decided against it. - bool bWantAssociateFiles = true; - if (!gPrefs->Read(wxT("/WantAssociateFiles"), &bWantAssociateFiles) || - bWantAssociateFiles) { - // Either there's no pref or user does want associations - // and they got stepped on, so ask. - int wantAssoc = - AudacityMessageBox( - XO( -"Audacity project (.AUP) files are not currently \nassociated with Audacity. \n\nAssociate them, so they open on double-click?"), - XO("Audacity Project Files"), - wxYES_NO | wxICON_QUESTION); - if (wantAssoc == wxYES) { - gPrefs->Write(wxT("/WantAssociateFiles"), true); - gPrefs->Flush(); - wxString root_key; + wxRegKey associateFileTypes; - root_key = wxT("HKCU\\Software\\Classes\\"); - associateFileTypes.SetName(root_key + wxT(".AUP")); // Start again with HKEY_CLASSES_ROOT. - if (!associateFileTypes.Create(true)) { - // Not at HKEY_CLASSES_USER. Try HKEY_CURRENT_ROOT. - root_key = wxT("HKCR\\"); - associateFileTypes.SetName(root_key + wxT(".AUP")); - if (!associateFileTypes.Create(true)) { - // Actually, can't create keys. Empty root_key to flag failure. - root_key.Empty(); - } - } - if (root_key.empty()) { - //v Warn that we can't set keys. Ask whether to set pref for no retry? - } else { - associateFileTypes = wxT("Audacity.Project"); // Finally set value for .AUP key + auto IsDefined = [&](const wxString &type) + { + associateFileTypes.SetName(wxString::Format(wxT("HKCR\\%s"), type)); + bool bKeyExists = associateFileTypes.Exists(); + if (!bKeyExists) + { + // Not at HKEY_CLASSES_ROOT. Try HKEY_CURRENT_USER. + associateFileTypes.SetName(wxString::Format(wxT("HKCU\\Software\\Classes\\%s"), type)); + bKeyExists = associateFileTypes.Exists(); + } + return bKeyExists; + }; - associateFileTypes.SetName(root_key + wxT("Audacity.Project")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - associateFileTypes = wxT("Audacity Project File"); - } + auto DefineType = [&](const wxString &type) + { + wxString root_key = wxT("HKCU\\Software\\Classes\\"); - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - associateFileTypes = wxT(""); - } - - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - } - - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\command")); - wxString tmpRegAudPath; - if(associateFileTypes.Exists()) { - tmpRegAudPath = associateFileTypes.QueryDefaultValue().Lower(); - } - if (!associateFileTypes.Exists() || - (tmpRegAudPath.Find(wxT("audacity.exe")) >= 0)) { - associateFileTypes.Create(true); - associateFileTypes = (wxString)argv[0] + (wxString)wxT(" \"%1\""); - } - -#if 0 - // These can be use later to support more startup messages - // like maybe "Import into existing project" or some such. - // Leaving here for an example... - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - associateFileTypes = wxT("%1"); - } - - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Application")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - associateFileTypes = IPC_APPL; - } - - associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Topic")); - if(!associateFileTypes.Exists()) { - associateFileTypes.Create(true); - associateFileTypes = IPC_TOPIC; - } -#endif - } - } else { - // User said no. Set a pref so we don't keep asking. - gPrefs->Write(wxT("/WantAssociateFiles"), false); - gPrefs->Flush(); + // Start with HKEY_CLASSES_CURRENT_USER. + associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type)); + if (!associateFileTypes.Create(true)) + { + // Not at HKEY_CLASSES_CURRENT_USER. Try HKEY_CURRENT_ROOT. + root_key = wxT("HKCR\\"); + associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type)); + if (!associateFileTypes.Create(true)) + { + // Actually, can't create keys. Empty root_key to flag failure. + root_key.empty(); } } + + if (!root_key.empty()) + { + associateFileTypes = wxT("Audacity.Project"); // Finally set value for the key + } + + return root_key; + }; + + // Check for legacy and UP types + if (IsDefined(wxT(".aup3")) && IsDefined(wxT(".aup")) && IsDefined(wxT("Audacity.Project"))) + { + // Already defined, so bail + return; + } + + // File types are not currently associated. + int wantAssoc = + AudacityMessageBox( + XO( +"Audacity project (.AUP) files are not currently \nassociated with Audacity. \n\nAssociate them, so they open on double-click?"), + XO("Audacity Project Files"), + wxYES_NO | wxICON_QUESTION); + + if (wantAssoc == wxNO) + { + // User said no. Set a pref so we don't keep asking. + gPrefs->Write(wxT("/WantAssociateFiles"), false); + gPrefs->Flush(); + return; + } + + // Show that user wants associations + gPrefs->Write(wxT("/WantAssociateFiles"), true); + gPrefs->Flush(); + + wxString root_key; + + root_key = DefineType(wxT(".aup3")); + if (root_key.empty()) + { + //v Warn that we can't set keys. Ask whether to set pref for no retry? + } + else + { + DefineType(wxT(".aup")); + + associateFileTypes = wxT("Audacity.Project"); // Finally set value for .AUP key + associateFileTypes.SetName(root_key + wxT("Audacity.Project")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + associateFileTypes = wxT("Audacity Project File"); + } + + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + associateFileTypes = wxT(""); + } + + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + } + + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\command")); + wxString tmpRegAudPath; + if(associateFileTypes.Exists()) + { + tmpRegAudPath = associateFileTypes.QueryDefaultValue().Lower(); + } + + if (!associateFileTypes.Exists() || + (tmpRegAudPath.Find(wxT("audacity.exe")) >= 0)) + { + associateFileTypes.Create(true); + associateFileTypes = (wxString)argv[0] + (wxString)wxT(" \"%1\""); + } + +#if 0 + // These can be use later to support more startup messages + // like maybe "Import into existing project" or some such. + // Leaving here for an example... + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + associateFileTypes = wxT("%1"); + } + + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Application")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + associateFileTypes = IPC_APPL; + } + + associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Topic")); + if (!associateFileTypes.Exists()) + { + associateFileTypes.Create(true); + associateFileTypes = IPC_TOPIC; + } +#endif } } #endif diff --git a/src/AudacityHeaders.h b/src/AudacityHeaders.h index 6be794fc5..4219dbab8 100644 --- a/src/AudacityHeaders.h +++ b/src/AudacityHeaders.h @@ -43,9 +43,7 @@ #include "AColor.h" #include "AudioIO.h" -#include "BlockFile.h" #include "Diags.h" -#include "DirManager.h" #include "Envelope.h" #include "FFT.h" #include "FileFormats.h" diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 0187e3962..4498375a5 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -455,7 +455,6 @@ time warp info and AudioIOListener and whether the playback is looped. #include #endif -#include "MissingAliasFileDialog.h" #include "Mix.h" #include "Resample.h" #include "RingBuffer.h" @@ -1824,9 +1823,6 @@ int AudioIO::StartStream(const TransportTracks &tracks, wxTheApp->ProcessEvent(e); } - // Enable warning popups for unfound aliased blockfiles. - MissingAliasFilesDialog::SetShouldShow(true); - commit = true; return mStreamToken; } @@ -2900,14 +2896,12 @@ void AudioIO::FillBuffers() { // Append captured samples to the end of the WaveTracks. // The WaveTracks have their own buffering for efficiency. - AutoSaveFile blockFileLog; auto numChannels = mCaptureTracks.size(); for( i = 0; i < numChannels; i++ ) { sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat(); - AutoSaveFile appendLog; size_t discarded = 0; if (!mRecordingSchedule.mLatencyCorrected) { @@ -2919,8 +2913,7 @@ void AudioIO::FillBuffers() size_t size = floor( correction * mRate * mFactor); SampleBuffer temp(size, trackFormat); ClearSamples(temp.ptr(), trackFormat, 0, size); - mCaptureTracks[i]->Append(temp.ptr(), trackFormat, - size, 1, &appendLog); + mCaptureTracks[i]->Append(temp.ptr(), trackFormat, size, 1); } else { // Leftward shift @@ -3022,19 +3015,7 @@ void AudioIO::FillBuffers() // Now append // see comment in second handler about guarantee - mCaptureTracks[i]->Append(temp.ptr(), format, - size, 1, - &appendLog); - - 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); - blockFileLog.EndTag(wxT("recordingrecovery")); - } + mCaptureTracks[i]->Append(temp.ptr(), format, size, 1); } // end loop over capture channels // Now update the recording shedule position @@ -3042,8 +3023,8 @@ void AudioIO::FillBuffers() mRecordingSchedule.mLatencyCorrected = latencyCorrected; auto pListener = GetListener(); - if (pListener && !blockFileLog.IsEmpty()) - pListener->OnAudioIONewBlockFiles(blockFileLog); + if (pListener) + pListener->OnAudioIONewBlockFiles(&mCaptureTracks); } // end of record buffering }, diff --git a/src/AudioIOListener.h b/src/AudioIOListener.h index bcf58f566..054e6ff65 100644 --- a/src/AudioIOListener.h +++ b/src/AudioIOListener.h @@ -27,7 +27,7 @@ public: virtual void OnAudioIOStartRecording() = 0; virtual void OnAudioIOStopRecording() = 0; - virtual void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) = 0; + virtual void OnAudioIONewBlockFiles(const WaveTrackArray *tracks) = 0; // Commit the addition of temporary recording tracks into the project virtual void OnCommitRecording() = 0; diff --git a/src/AutoRecovery.cpp b/src/AutoRecovery.cpp index 1f0fb5538..22fa5904e 100644 --- a/src/AutoRecovery.cpp +++ b/src/AutoRecovery.cpp @@ -8,10 +8,6 @@ *******************************************************************//** -\class AutoRecoveryDialog -\brief The AutoRecoveryDialog prompts the user whether to -recover previous Audacity projects that were closed incorrectly. - \class AutoSaveFile \brief a class wrapping reading and writing of arbitrary data in text or binary format to a file. @@ -20,189 +16,14 @@ text or binary format to a file. #include "Audacity.h" #include "AutoRecovery.h" -#include "DirManager.h" -#include "blockfile/SimpleBlockFile.h" -#include "Sequence.h" -#include -#include -#include -#include -#include - -#include "WaveClip.h" -#include "WaveTrack.h" - -//////////////////////////////////////////////////////////////////////////// -/// Recording recovery handler - -RecordingRecoveryHandler::RecordingRecoveryHandler(AudacityProject* proj) -{ - mProject = proj; - mChannel = -1; - mNumChannels = -1; -} - -int RecordingRecoveryHandler::FindTrack() const -{ - auto tracks = TrackList::Get( *mProject ).Any< const WaveTrack >(); - int index = 0; - if (mAutoSaveIdent) - { - auto iter = tracks.begin(), end = tracks.end(); - for (; iter != end; ++iter, ++index) - { - if ((*iter)->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; - } - - auto tracks = TrackList::Get( *mProject ).Any< WaveTrack >(); - 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; - } - - auto iter = tracks.begin(); - std::advance( iter, index ); - WaveTrack* track = *iter; - WaveClip* clip = track->NewestOrNewClip(); - Sequence* seq = clip->GetSequence(); - - // Load the blockfile from the XML - auto &dirManager = DirManager::Get( *mProject ); - dirManager.SetLoadingFormat(seq->GetSampleFormat()); - - BlockFilePtr blockFile; - dirManager.SetLoadingTarget( - [&]() -> BlockFilePtr& { return 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; -} - -void RecordingRecoveryHandler::HandleXMLEndTag(const wxChar *tag) -{ - if (wxStrcmp(tag, wxT("simpleblockfile")) == 0) - // Still in inner loop - return; - - auto tracks = TrackList::Get( *mProject ).Any< WaveTrack >(); - 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 { - auto iter = tracks.begin(); - std::advance( iter, index ); - WaveTrack* track = *iter; - WaveClip* clip = track->NewestOrNewClip(); - Sequence* seq = clip->GetSequence(); - - seq->ConsistencyCheck - (wxT("RecordingRecoveryHandler::HandleXMLEndTag"), false); - } -} - -XMLTagHandler* RecordingRecoveryHandler::HandleXMLChild(const wxChar *tag) -{ - if (wxStrcmp(tag, wxT("simpleblockfile")) == 0) - return this; // HandleXMLTag also handles - - return NULL; -} +#include /// /// 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. +// Simple "binary xml" format used exclusively for autosave documents. // // It is not intended that the user view or modify the file. // @@ -212,7 +33,7 @@ XMLTagHandler* RecordingRecoveryHandler::HandleXMLChild(const wxChar *tag) // // The file has 3 main sections: // -// ident literal "" +// character size 1 (UTF-8), 2 (UTF-16) or 4 (UTF-32) // name dictionary dictionary of all names used in the document // data fields the "encoded" XML document // @@ -225,10 +46,12 @@ XMLTagHandler* RecordingRecoveryHandler::HandleXMLChild(const wxChar *tag) // // 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. +// All name "lengths" are 2-byte signed, so are limited to 32767 bytes long. +// All strind/data "lengths" are 4-byte signed. enum FieldTypes { + FT_CharSize, // type, ID, value FT_StartTag, // type, ID FT_EndTag, // type, ID FT_String, // type, ID, string length, string @@ -246,6 +69,15 @@ enum FieldTypes FT_Name // type, ID, name length, name }; +// Static so that the dict can be reused each time. +// +// If entries get added later, like when an envelope node (for example) +// is writen and then the envelope is later removed, the dict will still +// contain the envelope name, but that's not a problem. + +NameMap AutoSaveFile::mNames; +wxMemoryBuffer AutoSaveFile::mDict; + TranslatableString AutoSaveFile::FailureMessage( const FilePath &/*filePath*/ ) { return @@ -255,7 +87,16 @@ XO("This recovery file was saved by Audacity 2.3.0 or before.\n" AutoSaveFile::AutoSaveFile(size_t allocSize) { - mAllocSize = allocSize; + mDict.SetBufSize(allocSize); + mBuffer.SetBufSize(allocSize); + + // Store the size of "wxChar" so we can convert during recovery in + // case the file is used on a system with a different character size. + char size = sizeof(wxChar); + mDict.AppendByte(FT_CharSize); + mDict.AppendData(&size, sizeof(size)); + + mDictChanged = false; } AutoSaveFile::~AutoSaveFile() @@ -264,13 +105,13 @@ AutoSaveFile::~AutoSaveFile() void AutoSaveFile::StartTag(const wxString & name) { - mBuffer.PutC(FT_StartTag); + mBuffer.AppendByte(FT_StartTag); WriteName(name); } void AutoSaveFile::EndTag(const wxString & name) { - mBuffer.PutC(FT_EndTag); + mBuffer.AppendByte(FT_EndTag); WriteName(name); } @@ -281,448 +122,396 @@ void AutoSaveFile::WriteAttr(const wxString & name, const wxChar *value) void AutoSaveFile::WriteAttr(const wxString & name, const wxString & value) { - mBuffer.PutC(FT_String); + mBuffer.AppendByte(FT_String); WriteName(name); int len = value.length() * sizeof(wxChar); - mBuffer.Write(&len, sizeof(len)); - mBuffer.Write(value.wx_str(), len); + mBuffer.AppendData(&len, sizeof(len)); + mBuffer.AppendData(value.wx_str(), len); } void AutoSaveFile::WriteAttr(const wxString & name, int value) { - mBuffer.PutC(FT_Int); + mBuffer.AppendByte(FT_Int); WriteName(name); - mBuffer.Write(&value, sizeof(value)); + mBuffer.AppendData(&value, sizeof(value)); } void AutoSaveFile::WriteAttr(const wxString & name, bool value) { - mBuffer.PutC(FT_Bool); + mBuffer.AppendByte(FT_Bool); WriteName(name); - mBuffer.Write(&value, sizeof(value)); + mBuffer.AppendData(&value, sizeof(value)); } void AutoSaveFile::WriteAttr(const wxString & name, long value) { - mBuffer.PutC(FT_Long); + mBuffer.AppendByte(FT_Long); WriteName(name); - mBuffer.Write(&value, sizeof(value)); + mBuffer.AppendData(&value, sizeof(value)); } void AutoSaveFile::WriteAttr(const wxString & name, long long value) { - mBuffer.PutC(FT_LongLong); + mBuffer.AppendByte(FT_LongLong); WriteName(name); - mBuffer.Write(&value, sizeof(value)); + mBuffer.AppendData(&value, sizeof(value)); } void AutoSaveFile::WriteAttr(const wxString & name, size_t value) { - mBuffer.PutC(FT_SizeT); + mBuffer.AppendByte(FT_SizeT); WriteName(name); - mBuffer.Write(&value, sizeof(value)); + mBuffer.AppendData(&value, sizeof(value)); } void AutoSaveFile::WriteAttr(const wxString & name, float value, int digits) { - mBuffer.PutC(FT_Float); + mBuffer.AppendByte(FT_Float); WriteName(name); - mBuffer.Write(&value, sizeof(value)); - mBuffer.Write(&digits, sizeof(digits)); + mBuffer.AppendData(&value, sizeof(value)); + mBuffer.AppendData(&digits, sizeof(digits)); } void AutoSaveFile::WriteAttr(const wxString & name, double value, int digits) { - mBuffer.PutC(FT_Double); + mBuffer.AppendByte(FT_Double); WriteName(name); - mBuffer.Write(&value, sizeof(value)); - mBuffer.Write(&digits, sizeof(digits)); + mBuffer.AppendData(&value, sizeof(value)); + mBuffer.AppendData(&digits, sizeof(digits)); } void AutoSaveFile::WriteData(const wxString & value) { - mBuffer.PutC(FT_Data); + mBuffer.AppendByte(FT_Data); int len = value.length() * sizeof(wxChar); - mBuffer.Write(&len, sizeof(len)); - mBuffer.Write(value.wx_str(), len); + mBuffer.AppendData(&len, sizeof(len)); + mBuffer.AppendData(value.wx_str(), len); } void AutoSaveFile::Write(const wxString & value) { - mBuffer.PutC(FT_Raw); + mBuffer.AppendByte(FT_Raw); int len = value.length() * sizeof(wxChar); - mBuffer.Write(&len, sizeof(len)); - mBuffer.Write(value.wx_str(), len); + mBuffer.AppendData(&len, sizeof(len)); + mBuffer.AppendData(value.wx_str(), len); } void AutoSaveFile::WriteSubTree(const AutoSaveFile & value) { - mBuffer.PutC(FT_Push); + mBuffer.AppendByte(FT_Push); - wxStreamBuffer *buf = value.mDict.GetOutputStreamBuffer(); - mBuffer.Write(buf->GetBufferStart(), buf->GetIntPosition()); + mBuffer.AppendData(value.mDict.GetData(), value.mDict.GetDataLen()); + mBuffer.AppendData(value.mBuffer.GetData(), value.mBuffer.GetDataLen()); - buf = value.mBuffer.GetOutputStreamBuffer(); - mBuffer.Write(buf->GetBufferStart(), buf->GetIntPosition()); - - mBuffer.PutC(FT_Pop); + mBuffer.AppendByte(FT_Pop); } bool AutoSaveFile::Write(wxFFile & file) const { - bool success = file.Write(AutoSaveIdent, strlen(AutoSaveIdent)) == strlen(AutoSaveIdent); - if (success) - { - success = Append(file); - } - - return success; + return Append(file); } bool AutoSaveFile::Append(wxFFile & file) const { - wxStreamBuffer *buf = mDict.GetOutputStreamBuffer(); - - bool success = file.Write(buf->GetBufferStart(), buf->GetIntPosition()) == buf->GetIntPosition(); + bool success = file.Write(mDict.GetData(), mDict.GetDataLen()) == mDict.GetDataLen(); if (success) { - buf = mBuffer.GetOutputStreamBuffer(); - success = file.Write(buf->GetBufferStart(), buf->GetIntPosition()) == buf->GetIntPosition(); + success = file.Write(mBuffer.GetData(), mBuffer.GetDataLen()) == mBuffer.GetDataLen(); } 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 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)) + auto nameiter = mNames.find(name); + if (nameiter != mNames.end()) { - id = mNames[name]; + id = nameiter->second; } else { + short len = name.length() * sizeof(wxChar); + 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); + mDict.AppendByte(FT_Name); + mDict.AppendData(&id, sizeof(id)); + mDict.AppendData(&len, sizeof(len)); + mDict.AppendData(name.wx_str(), len); + + mDictChanged = true; } - CheckSpace(mBuffer); - mBuffer.Write(&id, sizeof(id)); + mBuffer.AppendData(&id, sizeof(id)); +} + +const wxMemoryBuffer &AutoSaveFile::GetDict() const +{ + return mDict; +} + +const wxMemoryBuffer &AutoSaveFile::GetData() const +{ + return mBuffer; } bool AutoSaveFile::IsEmpty() const { - return mBuffer.GetLength() == 0; + return mBuffer.GetDataLen() == 0; } -bool AutoSaveFile::Decode(const FilePath & fileName) +bool AutoSaveFile::DictChanged() const { - 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 tag is preset. - - // Close the file so we can reopen it in read/write mode - file.Close(); - - // Add 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 - // "" 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, "") == 0) - { - // End of file does not contain closing tag, so add it - if (file.Seek(0, wxFromEnd)) - { - strcpy(buf, "\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, XO("Error Decoding File") }; - - IdMap mIds; - std::vector mIdStack; - - mIds.clear(); - - struct Error{}; - auto Lookup = [&mIds]( short id ) -> const wxString & { - auto iter = mIds.find( id ); - if ( iter == mIds.end() ) - throw Error{}; - return iter->second; - }; - - try { while ( !in.Eof() ) { - short id; - - switch (in.GetC()) - { - case FT_Push: - { - mIdStack.push_back(mIds); - mIds.clear(); - } - break; - - case FT_Pop: - { - mIds = mIdStack.back(); - mIdStack.pop_back(); - } - 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(Lookup(id)); - } - break; - - case FT_EndTag: - { - in.Read(&id, sizeof(id)); - - out.EndTag(Lookup(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(Lookup(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(Lookup(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(Lookup(id), val, dig); - } - break; - - case FT_Int: - { - int val; - - in.Read(&id, sizeof(id)); - in.Read(&val, sizeof(val)); - - out.WriteAttr(Lookup(id), val); - } - break; - - case FT_Bool: - { - bool val; - - in.Read(&id, sizeof(id)); - in.Read(&val, sizeof(val)); - - out.WriteAttr(Lookup(id), val); - } - break; - - case FT_Long: - { - long val; - - in.Read(&id, sizeof(id)); - in.Read(&val, sizeof(val)); - - out.WriteAttr(Lookup(id), val); - } - break; - - case FT_LongLong: - { - long long val; - - in.Read(&id, sizeof(id)); - in.Read(&val, sizeof(val)); - - out.WriteAttr(Lookup(id), val); - } - break; - - case FT_SizeT: - { - size_t val; - - in.Read(&id, sizeof(id)); - in.Read(&val, sizeof(val)); - - out.WriteAttr(Lookup(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; - } - } } - catch( const Error & ) - { - // return before committing, so we do not overwrite the recovery file! - return false; - } - - out.Commit(); - - return true; - } ); + return mDictChanged; +} + +wxString AutoSaveFile::Decode(const wxMemoryBuffer &buffer) +{ + wxMemoryInputStream in(buffer.GetData(), buffer.GetDataLen()); + + XMLStringWriter out; + + std::vector bytes; + IdMap mIds; + std::vector mIdStack; + char mCharSize = 0; + + mIds.clear(); + + struct Error{}; + auto Lookup = [&mIds]( short id ) -> const wxString & + { + auto iter = mIds.find( id ); + if (iter == mIds.end()) + { + throw Error{}; + } + return iter->second; + }; + + auto Convert = [&mCharSize](char *in, int len) -> wxString + { + wxUString str; + + switch (mCharSize) + { + case 1: + str.assignFromUTF8(in, len); + break; + + case 2: + str.assignFromUTF16((wxChar16 *) in, len / 2); + break; + + case 4: + { + str = wxU32CharBuffer::CreateNonOwned((wxChar32 *) in, len / 4); + } + break; + + default: + wxASSERT_MSG(false, wxT("Characters size not 1, 2, or 4")); + } + + return str; + }; + + while (!in.Eof()) + { + short id; + + switch (in.GetC()) + { + case FT_Push: + { + mIdStack.push_back(mIds); + mIds.clear(); + } + break; + + case FT_Pop: + { + mIds = mIdStack.back(); + mIdStack.pop_back(); + } + break; + + case FT_Name: + { + short len; + + in.Read(&id, sizeof(id)); + in.Read(&len, sizeof(len)); + bytes.reserve(len); + in.Read(bytes.data(), len); + + mIds[id] = Convert(bytes.data(), len); + } + break; + + case FT_StartTag: + { + in.Read(&id, sizeof(id)); + + out.StartTag(Lookup(id)); + } + break; + + case FT_EndTag: + { + in.Read(&id, sizeof(id)); + + out.EndTag(Lookup(id)); + } + break; + + case FT_String: + { + int len; + + in.Read(&id, sizeof(id)); + in.Read(&len, sizeof(len)); + bytes.reserve(len); + in.Read(bytes.data(), len); + + out.WriteAttr(Lookup(id), Convert(bytes.data(), len)); + } + 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(Lookup(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(Lookup(id), val, dig); + } + break; + + case FT_Int: + { + int val; + + in.Read(&id, sizeof(id)); + in.Read(&val, sizeof(val)); + + out.WriteAttr(Lookup(id), val); + } + break; + + case FT_Bool: + { + bool val; + + in.Read(&id, sizeof(id)); + in.Read(&val, sizeof(val)); + + out.WriteAttr(Lookup(id), val); + } + break; + + case FT_Long: + { + long val; + + in.Read(&id, sizeof(id)); + in.Read(&val, sizeof(val)); + + out.WriteAttr(Lookup(id), val); + } + break; + + case FT_LongLong: + { + long long val; + + in.Read(&id, sizeof(id)); + in.Read(&val, sizeof(val)); + + out.WriteAttr(Lookup(id), val); + } + break; + + case FT_SizeT: + { + size_t val; + + in.Read(&id, sizeof(id)); + in.Read(&val, sizeof(val)); + + out.WriteAttr(Lookup(id), val); + } + break; + + case FT_Data: + { + int len; + + in.Read(&len, sizeof(len)); + bytes.reserve(len); + in.Read(bytes.data(), len); + + out.WriteData(Convert(bytes.data(), len)); + } + break; + + case FT_Raw: + { + int len; + + in.Read(&len, sizeof(len)); + bytes.reserve(len); + in.Read(bytes.data(), len); + + out.Write(Convert(bytes.data(), len)); + } + break; + + case FT_CharSize: + { + in.Read(&mCharSize, sizeof(mCharSize)); + } + break; + + default: + wxASSERT(true); + break; + } + } + + return out; } diff --git a/src/AutoRecovery.h b/src/AutoRecovery.h index f6575941b..139918e05 100644 --- a/src/AutoRecovery.h +++ b/src/AutoRecovery.h @@ -18,39 +18,10 @@ #include #include "audacity/Types.h" -class wxFFile; -class AudacityProject; - -// -// XML Handler for a tag -// -class RecordingRecoveryHandler final : public XMLTagHandler -{ -public: - RecordingRecoveryHandler(AudacityProject* proj); - bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override; - void HandleXMLEndTag(const wxChar *tag) override; - XMLTagHandler *HandleXMLChild(const wxChar *tag) override; - - // This class only knows reading tags - -private: - - int FindTrack() const; - - AudacityProject* mProject; - int mChannel; - int mNumChannels; - int mAutoSaveIdent; -}; - /// /// AutoSaveFile /// -// Should be plain ASCII -#define AutoSaveIdent "" - using NameMap = std::unordered_map; using IdMap = std::unordered_map; @@ -87,20 +58,23 @@ public: bool Write(wxFFile & file) const; bool Append(wxFFile & file) const; - bool IsEmpty() const; + const wxMemoryBuffer &GetDict() const; + const wxMemoryBuffer &GetData() const; - bool Decode(const FilePath & fileName); + bool IsEmpty() const; + bool DictChanged() const; + + static wxString Decode(const wxMemoryBuffer &buffer); private: void WriteName(const wxString & name); - void CheckSpace(wxMemoryOutputStream & buf); private: - wxMemoryOutputStream mBuffer; - wxMemoryOutputStream mDict; - NameMap mNames; - size_t mAllocSize; + wxMemoryBuffer mBuffer; + bool mDictChanged; + + static NameMap mNames; + static wxMemoryBuffer mDict; }; - #endif diff --git a/src/AutoRecoveryDialog.cpp b/src/AutoRecoveryDialog.cpp index c925e221b..6b95519f9 100644 --- a/src/AutoRecoveryDialog.cpp +++ b/src/AutoRecoveryDialog.cpp @@ -31,26 +31,28 @@ enum { class AutoRecoveryDialog final : public wxDialogWrapper { public: - AutoRecoveryDialog(wxWindow *parent); + AutoRecoveryDialog(const FilePaths &files); private: + void PopulateOrExchange(ShuttleGui &S); void PopulateList(); - void PopulateOrExchange(ShuttleGui & S); void OnQuitAudacity(wxCommandEvent &evt); void OnRecoverNone(wxCommandEvent &evt); void OnRecoverAll(wxCommandEvent &evt); + const FilePaths & mFiles; wxListCtrl *mFileList; public: DECLARE_EVENT_TABLE() }; -AutoRecoveryDialog::AutoRecoveryDialog(wxWindow *parent) : - wxDialogWrapper(parent, -1, XO("Automatic Crash Recovery"), - wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE & (~wxCLOSE_BOX)) // no close box +AutoRecoveryDialog::AutoRecoveryDialog(const FilePaths &files) +: wxDialogWrapper(nullptr, -1, XO("Automatic Crash Recovery"), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE & (~wxCLOSE_BOX)), // no close box + mFiles(files) { SetName(); ShuttleGui S(this, eIsCreating); @@ -63,14 +65,13 @@ BEGIN_EVENT_TABLE(AutoRecoveryDialog, wxDialogWrapper) EVT_BUTTON(ID_QUIT_AUDACITY, AutoRecoveryDialog::OnQuitAudacity) END_EVENT_TABLE() -void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui& S) +void AutoRecoveryDialog::PopulateOrExchange(ShuttleGui &S) { S.SetBorder(5); S.StartVerticalLay(); { S.AddVariableText( - XO( -"Some projects were not saved properly the last time Audacity was run.\nFortunately, the following projects can be automatically recovered:"), + XO("Some projects were not saved properly the last time Audacity was run.\nFortunately, the following projects can be automatically recovered:"), false); S.StartStatic(XO("Recoverable projects")); @@ -110,15 +111,10 @@ 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()); + for (int i = 0, cnt = mFiles.size(); i < cnt; ++i) + { + mFileList->InsertItem(i, wxFileName{ mFiles[i] }.GetName()); + } mFileList->SetColumnWidth(0, wxLIST_AUTOSIZE); } @@ -131,8 +127,7 @@ void AutoRecoveryDialog::OnQuitAudacity(wxCommandEvent & WXUNUSED(event)) void AutoRecoveryDialog::OnRecoverNone(wxCommandEvent & WXUNUSED(event)) { int ret = AudacityMessageBox( - XO( -"Are you sure you want to discard all recoverable projects?\n\nChoosing \"Yes\" discards all recoverable projects immediately."), + XO("Are you sure you want to discard all recoverable projects?\n\nChoosing \"Yes\" discards all recoverable projects immediately."), XO("Confirm Discard Projects"), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT, this); @@ -147,69 +142,54 @@ void AutoRecoveryDialog::OnRecoverAll(wxCommandEvent & WXUNUSED(event)) //////////////////////////////////////////////////////////////////////////// -static bool HaveFilesToRecover() +static FilePaths HaveFilesToRecover() { - wxDir dir(FileNames::AutoSaveDir()); - if (!dir.IsOpened()) - { - AudacityMessageBox( - XO("Could not enumerate files in auto save directory."), - XO("Error"), - wxICON_STOP); - return false; - } + wxString tempdir = FileNames::TempDir(); + wxString pattern = wxT("*.") + FileNames::UnsavedProjectExtension(); + FilePaths files; - wxString filename; - bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES); + wxDir::GetAllFiles(tempdir, &files, pattern, wxDIR_FILES); - return c; + return files; } -static bool RemoveAllAutoSaveFiles() +static bool RemoveAllAutoSaveFiles(const FilePaths &files) { - FilePaths files; - wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files, - wxT("*.autosave"), wxDIR_FILES); - - for (unsigned int i = 0; i < files.size(); i++) + for (int i = 0, cnt = files.size(); i < cnt; ++i) { - if (!wxRemoveFile(files[i])) + FilePath file = files[i]; + + if (wxRemoveFile(file)) { - // I don't think this error message is actually useful. - // -dmazzoni - //AudacityMessageBox( - // XO("Could not remove auto save file: %s".Format( files[i] ), - // XO("Error"), - // wxICON_STOP); - return false; + if (wxFileExists(file + wxT("-shm"))) + { + wxRemoveFile(file + wxT("-shm")); + } + + if (wxFileExists(file + wxT("-wal"))) + { + wxRemoveFile(file + wxT("-wal")); + } + + if (wxFileExists(file + wxT("-journal"))) + { + wxRemoveFile(file + wxT("-journal")); + } } } return true; } -static bool RecoverAllProjects(AudacityProject** pproj) +static bool RecoverAllProjects(const FilePaths &files, + AudacityProject **pproj) { - wxDir dir(FileNames::AutoSaveDir()); - if (!dir.IsOpened()) - { - AudacityMessageBox( - XO("Could not enumerate files in auto save directory."), - XO("Error"), - wxICON_STOP); - return false; - } - // Open a project window for each auto save file wxString filename; - FilePaths files; - wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files, - wxT("*.autosave"), wxDIR_FILES); - - for (unsigned int i = 0; i < files.size(); i++) + for (int i = 0, cnt = files.size(); i < cnt; ++i) { - AudacityProject* proj{}; + AudacityProject *proj = nullptr; if (*pproj) { // Reuse existing project window @@ -220,18 +200,20 @@ static bool RecoverAllProjects(AudacityProject** pproj) // 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. - (void) ProjectManager::OpenProject( proj, files[i], false ); + (void) ProjectManager::OpenProject(proj, files[i], false); } return true; } -bool ShowAutoRecoveryDialogIfNeeded(AudacityProject** pproj, +bool ShowAutoRecoveryDialogIfNeeded(AudacityProject **pproj, bool *didRecoverAnything) { if (didRecoverAnything) *didRecoverAnything = false; - if (HaveFilesToRecover()) + + FilePaths files = HaveFilesToRecover(); + if (files.size()) { // Under wxGTK3, the auto recovery dialog will not get // the focus since the project window hasn't been allowed @@ -247,17 +229,17 @@ bool ShowAutoRecoveryDialogIfNeeded(AudacityProject** pproj, // This must be done before "dlg" is declared. wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI); - int ret = AutoRecoveryDialog{nullptr}.ShowModal(); + int ret = AutoRecoveryDialog(files).ShowModal(); switch (ret) { case ID_RECOVER_NONE: - return RemoveAllAutoSaveFiles(); + return RemoveAllAutoSaveFiles(files); case ID_RECOVER_ALL: if (didRecoverAnything) *didRecoverAnything = true; - return RecoverAllProjects(pproj); + return RecoverAllProjects(files, pproj); default: // This includes ID_QUIT_AUDACITY diff --git a/src/BatchCommands.cpp b/src/BatchCommands.cpp index ffbf4beaa..e99ed849b 100644 --- a/src/BatchCommands.cpp +++ b/src/BatchCommands.cpp @@ -25,6 +25,7 @@ processing. See also MacrosWindow and ApplyMacroDialog. #include "Project.h" #include "ProjectAudioManager.h" +#include "ProjectFileIO.h" #include "ProjectHistory.h" #include "ProjectSettings.h" #include "ProjectWindow.h" @@ -631,6 +632,7 @@ bool MacroCommands::ApplySpecialCommand( return true; AudacityProject *project = &mProject; + auto &projectFileIO = ProjectFileIO::Get( *project ); unsigned numChannels = 1; //used to switch between mono and stereo export if (IsMono( &mProject )) { @@ -650,7 +652,7 @@ bool MacroCommands::ApplySpecialCommand( else extension = wxT("mp3"); if (mFileName.empty()) { - filename = BuildCleanFileName(project->GetFileName(), extension); + filename = BuildCleanFileName(projectFileIO.GetFileName(), extension); } else { filename = BuildCleanFileName(mFileName, extension); diff --git a/src/Benchmark.cpp b/src/Benchmark.cpp index e317a0120..dc5233392 100644 --- a/src/Benchmark.cpp +++ b/src/Benchmark.cpp @@ -33,7 +33,6 @@ of the BlockFile system. #include #include -#include "DirManager.h" #include "ShuttleGui.h" #include "Project.h" #include "WaveClip.h" @@ -51,7 +50,7 @@ class BenchmarkDialog final : public wxDialogWrapper { public: // constructors and destructors - BenchmarkDialog( wxWindow *parent, const ProjectSettings &settings ); + BenchmarkDialog( wxWindow *parent, AudacityProject &project ); void MakeBenchmarkDialog(); @@ -66,6 +65,7 @@ private: void HoldPrint(bool hold); void FlushPrint(); + AudacityProject &mProject; const ProjectSettings &mSettings; bool mHoldPrint; @@ -85,7 +85,7 @@ private: DECLARE_EVENT_TABLE() }; -void RunBenchmark( wxWindow *parent, const ProjectSettings &settings ) +void RunBenchmark( wxWindow *parent, AudacityProject &project ) { /* int action = AudacityMessageBox( @@ -101,7 +101,7 @@ XO("This will close all project windows (without saving)\nand open the Audacity GetProjectFrame( *pProject ).Close(); */ - BenchmarkDialog dlog{ parent, settings }; + BenchmarkDialog dlog{ parent, project }; dlog.CentreOnParent(); @@ -131,14 +131,15 @@ BEGIN_EVENT_TABLE(BenchmarkDialog, wxDialogWrapper) END_EVENT_TABLE() BenchmarkDialog::BenchmarkDialog( - wxWindow *parent, const ProjectSettings &settings) + wxWindow *parent, AudacityProject &project) : /* i18n-hint: Benchmark means a software speed test */ wxDialogWrapper( parent, 0, XO("Benchmark"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , mSettings{ settings } + , mProject(project) + , mSettings{ ProjectSettings::Get(project) } { SetName(); @@ -366,9 +367,10 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event)) HoldPrint(true); ZoomInfo zoomInfo(0.0, ZoomInfo::GetDefaultZoom()); - auto dd = DirManager::Create(); const auto t = - TrackFactory{ mSettings, dd, &zoomInfo }.NewWaveTrack(int16Sample); + TrackFactory{ mSettings, + mProject, + &zoomInfo }.NewWaveTrack(int16Sample); t->SetRate(1); @@ -570,8 +572,6 @@ void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event)) success: - dd.reset(); - Printf( XO("Benchmark completed successfully.\n") ); HoldPrint(false); } diff --git a/src/Benchmark.h b/src/Benchmark.h index e63c278b3..35022e8f7 100644 --- a/src/Benchmark.h +++ b/src/Benchmark.h @@ -11,8 +11,8 @@ #ifndef __AUDACITY_BENCHMARK__ #define __AUDACITY_BENCHMARK__ -class ProjectSettings; +class AudacityProject; -void RunBenchmark( wxWindow *parent, const ProjectSettings &settings ); +void RunBenchmark( wxWindow *parent, AudacityProject &project ); #endif // define __AUDACITY_BENCHMARK__ diff --git a/src/BlockFile.cpp b/src/BlockFile.cpp deleted file mode 100644 index 4a3227155..000000000 --- a/src/BlockFile.cpp +++ /dev/null @@ -1,824 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - BlockFile.cpp - - Joshua Haberman - Dominic Mazzoni - -*******************************************************************//*! - -\class BlockFile -\brief A BlockFile is a chunk of immutable audio data. - -A BlockFile represents a chunk of audio data. These chunks are -assembled into sequences by the class Sequence. These classes -are at the heart of how Audacity stores audio data. - -BlockFile is an abstract base class that can be implemented in -many different ways. However it does have a fairly large amount -of shared code that deals with the physical file and manipulating -the summary data. - -BlockFile should be thought of as an immutable class. After it -is constructed, it is essentially never changed (though there are -a few exceptions). Most notably, the audio data and summary data -are never altered once it is constructed. This is important to -some of the derived classes that are actually aliases to audio -data stored in existing files. - -BlockFiles are managed by std::shared_ptr, so they are reference-counted. - -*//****************************************************************//** - -\class SummaryInfo -\brief Works with BlockFile to hold info about max and min and RMS -over multiple samples, which in turn allows rapid drawing when zoomed -out. - -*//*******************************************************************/ - -#include "Audacity.h" -#include "BlockFile.h" - -#include -#include - -#include -#include -#include -#include - -#include "sndfile.h" -#include "FileException.h" -#include "FileFormats.h" - -// msmeyer: Define this to add debug output via wxPrintf() -//#define DEBUG_BLOCKFILE - -#ifdef DEBUG_BLOCKFILE -#define BLOCKFILE_DEBUG_OUTPUT(op, i) \ - wxPrintf(wxT("[BlockFile %x %s] %s: %i\n"), (unsigned)this, \ - mFileName.GetFullName(), wxT(op), i); -#else -#define BLOCKFILE_DEBUG_OUTPUT(op, i) -#endif - -static const int headerTagLen = 20; -static char headerTag[headerTagLen + 1] = "AudacityBlockFile112"; - -SummaryInfo::SummaryInfo(size_t samples) -{ - format = floatSample; - - fields = 3; /* min, max, rms */ - - bytesPerFrame = sizeof(float) * fields; - - frames64K = (samples + 65535) / 65536; - frames256 = frames64K * 256; - - offset64K = headerTagLen; - offset256 = offset64K + (frames64K * bytesPerFrame); - totalSummaryBytes = offset256 + (frames256 * bytesPerFrame); -} - -ArrayOf BlockFile::fullSummary; - -/// Initializes the base BlockFile data. The block is initially -/// unlocked and its reference count is 1. -/// -/// @param fileName The name of the disk file associated with this -/// BlockFile. Not all BlockFiles will store their -/// sample data here (for example, AliasBlockFiles -/// read their data from elsewhere), but all BlockFiles -/// will store at least the summary data here. -/// -/// @param samples The number of samples this BlockFile contains. -BlockFile::BlockFile(wxFileNameWrapper &&fileName, size_t samples): - mLockCount(0), - mFileName(std::move(fileName)), - mLen(samples), - mSummaryInfo(samples) -{ - mSilentLog=FALSE; -} - -// static -unsigned long BlockFile::gBlockFileDestructionCount { 0 }; - -BlockFile::~BlockFile() -{ - if (!IsLocked() && mFileName.HasName()) - // PRL: what should be done if this fails? - wxRemoveFile(mFileName.GetFullPath()); - - ++gBlockFileDestructionCount; -} - -/// Returns the file name of the disk file associated with this -/// BlockFile. Not all BlockFiles store their sample data here, -/// but most BlockFiles have at least their summary data here. -/// (some, i.e. SilentBlockFiles, do not correspond to a file on -/// disk and have empty file names) -auto BlockFile::GetFileName() const -> GetFileNameResult -{ - return { mFileName }; -} - -///sets the file name the summary info will be saved in. threadsafe. -void BlockFile::SetFileName(wxFileNameWrapper &&name) -{ - mFileName=std::move(name); -} - -const wxFileNameWrapper &BlockFile::GetExternalFileName() const -{ - static wxFileNameWrapper empty; - return empty; -} - -void BlockFile::SetExternalFileName( wxFileNameWrapper && ) -{ - wxASSERT( false ); -} - -/// Marks this BlockFile as "locked." A locked BlockFile may not -/// be moved or deleted, only copied. Locking a BlockFile prevents -/// it from disappearing if the project is saved in a different location. -/// When doing a "Save As," Audacity locks all blocks belonging -/// to the already-existing project, to ensure that the existing -/// project remains valid with all the blocks it needs. Audacity -/// also locks the blocks of the last saved version of a project when -/// the project is deleted so that the files aren't deleted when their -/// refcount hits zero. -void BlockFile::Lock() -{ - mLockCount++; - BLOCKFILE_DEBUG_OUTPUT("Lock", mLockCount); -} - -/// Marks this BlockFile as "unlocked." -void BlockFile::Unlock() -{ - mLockCount--; - BLOCKFILE_DEBUG_OUTPUT("Unlock", mLockCount); -} - -/// Returns true if the block is locked. -bool BlockFile::IsLocked() -{ - return mLockCount > 0; -} - -/// Get a buffer containing a summary block describing this sample -/// data. This must be called by derived classes when they -/// are constructed, to allow them to construct their summary data, -/// after which they should write that data to their disk file. -/// -/// This method also has the side effect of setting the mMin, mMax, -/// and mRMS members of this class. -/// -/// You must not DELETE the returned buffer; it is static to this -/// method. -/// -/// @param buffer A buffer containing the sample data to be analyzed -/// @param len The length of the sample data -/// @param format The format of the sample data. -void *BlockFile::CalcSummary(samplePtr buffer, size_t len, - sampleFormat format, ArrayOf &cleanup) -{ - // Caller has nothing to deallocate - cleanup.reset(); - - fullSummary.reinit(mSummaryInfo.totalSummaryBytes); - - memcpy(fullSummary.get(), headerTag, headerTagLen); - - float *summary64K = (float *)(fullSummary.get() + mSummaryInfo.offset64K); - float *summary256 = (float *)(fullSummary.get() + mSummaryInfo.offset256); - - Floats fbuffer{ len }; - CopySamples(buffer, format, - (samplePtr)fbuffer.get(), floatSample, len); - - CalcSummaryFromBuffer(fbuffer.get(), len, summary256, summary64K); - - return fullSummary.get(); -} - -void BlockFile::CalcSummaryFromBuffer(const float *fbuffer, size_t len, - float *summary256, float *summary64K) -{ - decltype(len) sumLen; - - float min, max; - float sumsq; - double totalSquares = 0.0; - double fraction { 0.0 }; - - // Recalc 256 summaries - sumLen = (len + 255) / 256; - int summaries = 256; - - for (decltype(sumLen) i = 0; i < sumLen; i++) { - min = fbuffer[i * 256]; - max = fbuffer[i * 256]; - sumsq = ((float)min) * ((float)min); - decltype(len) jcount = 256; - if (jcount > len - i * 256) { - jcount = len - i * 256; - fraction = 1.0 - (jcount / 256.0); - } - for (decltype(jcount) j = 1; j < jcount; j++) { - float f1 = fbuffer[i * 256 + j]; - sumsq += ((float)f1) * ((float)f1); - if (f1 < min) - min = f1; - else if (f1 > max) - max = f1; - } - - totalSquares += sumsq; - float rms = (float)sqrt(sumsq / jcount); - - summary256[i * 3] = min; - summary256[i * 3 + 1] = max; - summary256[i * 3 + 2] = rms; // The rms is correct, but this may be for less than 256 samples in last loop. - } - for (auto i = sumLen; i < mSummaryInfo.frames256; i++) { - // filling in the remaining bits with non-harming/contributing values - // rms values are not "non-harming", so keep count of them: - summaries--; - summary256[i * 3] = FLT_MAX; // min - summary256[i * 3 + 1] = -FLT_MAX; // max - summary256[i * 3 + 2] = 0.0f; // rms - } - - // Calculate now while we can do it accurately - mRMS = sqrt(totalSquares/len); - - // Recalc 64K summaries - sumLen = (len + 65535) / 65536; - - for (decltype(sumLen) i = 0; i < sumLen; i++) { - min = summary256[3 * i * 256]; - max = summary256[3 * i * 256 + 1]; - sumsq = (float)summary256[3 * i * 256 + 2]; - sumsq *= sumsq; - for (decltype(len) j = 1; j < 256; j++) { // we can overflow the useful summary256 values here, but have put non-harmful values in them - if (summary256[3 * (i * 256 + j)] < min) - min = summary256[3 * (i * 256 + j)]; - if (summary256[3 * (i * 256 + j) + 1] > max) - max = summary256[3 * (i * 256 + j) + 1]; - float r1 = summary256[3 * (i * 256 + j) + 2]; - sumsq += r1*r1; - } - - double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction; - float rms = (float)sqrt(sumsq / denom); - - summary64K[i * 3] = min; - summary64K[i * 3 + 1] = max; - summary64K[i * 3 + 2] = rms; - } - for (auto i = sumLen; i < mSummaryInfo.frames64K; i++) { - wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here? - summary64K[i * 3] = 0.0f; // probably should be FLT_MAX, need a test case - summary64K[i * 3 + 1] = 0.0f; // probably should be -FLT_MAX, need a test case - summary64K[i * 3 + 2] = 0.0f; // just padding - } - - // Recalc block-level summary (mRMS already calculated) - min = summary64K[0]; - max = summary64K[1]; - - for (decltype(sumLen) i = 1; i < sumLen; i++) { - if (summary64K[3*i] < min) - min = summary64K[3*i]; - if (summary64K[3*i+1] > max) - max = summary64K[3*i+1]; - } - - mMin = min; - mMax = max; -} - -static void ComputeMinMax256(float *summary256, - float *outMin, float *outMax, int *outBads) -{ - float min, max; - int i; - int bad = 0; - - min = 1.0; - max = -1.0; - for(i=0; i<256; i++) { - if (summary256[3*i] < min) - min = summary256[3*i]; - else if (!(summary256[3*i] >= min)) - bad++; - if (summary256[3*i+1] > max) - max = summary256[3*i+1]; - else if (!(summary256[3*i+1] <= max)) - bad++; - if (std::isnan(summary256[3*i+2])) - bad++; - if (summary256[3*i+2] < -1 || summary256[3*i+2] > 1) - bad++; - } - - *outMin = min; - *outMax = max; - *outBads = bad; -} - -/// Byte-swap the summary data, in case it was saved by a system -/// on a different platform -void BlockFile::FixSummary(void *data) -{ - if (mSummaryInfo.format != floatSample || - mSummaryInfo.fields != 3) - return; - - // These pointers point into extra 'unused' preamble space in the - // .au file. WE are using that space for summary data. - float *summary64K = (float *)((char *)data + mSummaryInfo.offset64K); - float *summary256 = (float *)((char *)data + mSummaryInfo.offset256); - - float min, max; - int bad; - - // Bug 2433 fix - // Previosuly we computed min and max, and a discrepancy between 64K - // summary and 256 sample summary contributed towards indicating an - // error (a byte swapped .au file, probably from a machine - // with the opposite architecture). - // - // However, that min-max test was incorrect on two counts: - // 1. Both the 256 and 64K data streams are byte-swapped or not - // together - so a consistency check served no useful purpose. - // 2. On 64 bit architecture with function inlining, calculations - // could end up being done at higher precision on one variable, - // resulting in a spurious mismatch. Exact comparison of floats - // can be problematic. - // - // Point 2 led to bug 2433 when functions were inlined, and .au - // files not being byte swapped when they should be. - // - // The fix is to not use the min max in the consistency test, and just - // use the 'bad' count which is an integer. We aim for no bad samples. - ComputeMinMax256(summary256, &min, &max, &bad); - - if ( bad > 0) { - unsigned int *buffer = (unsigned int *)data; - auto len = mSummaryInfo.totalSummaryBytes / 4; - - for(unsigned int i=0; i MinMaxRMS -{ - // TODO: actually use summaries - SampleBuffer blockData(len, floatSample); - - this->ReadData(blockData.ptr(), floatSample, start, len, mayThrow); - - float min = FLT_MAX; - float max = -FLT_MAX; - float sumsq = 0; - - for( decltype(len) i = 0; i < len; i++ ) - { - float sample = ((float*)blockData.ptr())[i]; - - if( sample > max ) - max = sample; - if( sample < min ) - min = sample; - sumsq += (sample*sample); - } - - return { min, max, (float)sqrt(sumsq/len) }; -} - -/// Retrieves the minimum, maximum, and maximum RMS of this entire -/// block. This is faster than the other GetMinMax function since -/// these values are already computed. -auto BlockFile::GetMinMaxRMS(bool) - const -> MinMaxRMS -{ - return { mMin, mMax, mRMS }; -} - -/// Retrieves a portion of the 256-byte summary buffer from this BlockFile. This -/// data provides information about the minimum value, the maximum -/// value, and the maximum RMS value for every group of 256 samples in the -/// file. -/// Fill with zeroes and return false if data are unavailable for any reason. -/// -/// -/// @param *buffer The area where the summary information will be -/// written. It must be at least len*3 long. -/// @param start The offset in 256-sample increments -/// @param len The number of 256-sample summary frames to read -bool BlockFile::Read256(float *buffer, - size_t start, size_t len) -{ - wxASSERT(start >= 0); - - ArrayOf< char > summary; - // In case of failure, summary is filled with zeroes - auto result = this->ReadSummary(summary); - - start = std::min( start, mSummaryInfo.frames256 ); - len = std::min( len, mSummaryInfo.frames256 - start ); - - CopySamples(summary.get() + mSummaryInfo.offset256 + - (start * mSummaryInfo.bytesPerFrame), - mSummaryInfo.format, - (samplePtr)buffer, floatSample, len * mSummaryInfo.fields); - - if (mSummaryInfo.fields == 2) { - // No RMS info; make guess - for(auto i = len; i--;) { - buffer[3*i+2] = (fabs(buffer[2*i]) + fabs(buffer[2*i+1]))/4.0; - buffer[3*i+1] = buffer[2*i+1]; - buffer[3*i] = buffer[2*i]; - } - } - - return result; -} - -/// Retrieves a portion of the 64K summary buffer from this BlockFile. This -/// data provides information about the minimum value, the maximum -/// value, and the maximum RMS value for every group of 64K samples in the -/// file. -/// Fill with zeroes and return false if data are unavailable for any reason. -/// -/// @param *buffer The area where the summary information will be -/// written. It must be at least len*3 long. -/// @param start The offset in 64K-sample increments -/// @param len The number of 64K-sample summary frames to read -bool BlockFile::Read64K(float *buffer, - size_t start, size_t len) -{ - wxASSERT(start >= 0); - - ArrayOf< char > summary; - // In case of failure, summary is filled with zeroes - auto result = this->ReadSummary(summary); - - start = std::min( start, mSummaryInfo.frames64K ); - len = std::min( len, mSummaryInfo.frames64K - start ); - - CopySamples(summary.get() + mSummaryInfo.offset64K + - (start * mSummaryInfo.bytesPerFrame), - mSummaryInfo.format, - (samplePtr)buffer, floatSample, len * mSummaryInfo.fields); - - if (mSummaryInfo.fields == 2) { - // No RMS info; make guess - for(auto i = len; i--;) { - buffer[3*i+2] = (fabs(buffer[2*i]) + fabs(buffer[2*i+1]))/4.0; - buffer[3*i+1] = buffer[2*i+1]; - buffer[3*i] = buffer[2*i]; - } - } - - return result; -} - -namespace { - BlockFile::MissingAliasFileFoundHook &GetMissingAliasFileFound() - { - static BlockFile::MissingAliasFileFoundHook theHook; - return theHook; - } -} - -auto BlockFile::SetMissingAliasFileFound( MissingAliasFileFoundHook hook ) - -> MissingAliasFileFoundHook -{ - auto &theHook = GetMissingAliasFileFound(); - auto result = theHook; - theHook = hook; - return result; -} - -size_t BlockFile::CommonReadData( - bool mayThrow, - const wxFileName &fileName, bool &mSilentLog, - const AliasBlockFile *pAliasFile, sampleCount origin, unsigned channel, - samplePtr data, sampleFormat format, size_t start, size_t len, - const sampleFormat *pLegacyFormat, size_t legacyLen) -{ - // Third party library has its own type alias, check it before - // adding origin + size_t - static_assert(sizeof(sampleCount::type) <= sizeof(sf_count_t), - "Type sf_count_t is too narrow to hold a sampleCount"); - - SF_INFO info; - memset(&info, 0, sizeof(info)); - - if ( pLegacyFormat ) { - switch( *pLegacyFormat ) { - case int16Sample: - info.format = - SF_FORMAT_RAW | SF_FORMAT_PCM_16 | SF_ENDIAN_CPU; - break; - default: - case floatSample: - info.format = - SF_FORMAT_RAW | SF_FORMAT_FLOAT | SF_ENDIAN_CPU; - break; - case int24Sample: - info.format = SF_FORMAT_RAW | SF_FORMAT_PCM_32 | SF_ENDIAN_CPU; - break; - } - info.samplerate = 44100; // Doesn't matter - info.channels = 1; - info.frames = legacyLen + origin.as_long_long(); - } - - - wxFile f; // will be closed when it goes out of scope - SFFile sf; - - { - Optional silence{}; - if (mSilentLog) - silence.emplace(); - - const auto fullPath = fileName.GetFullPath(); - if (wxFile::Exists(fullPath) && f.Open(fullPath)) { - // Even though there is an sf_open() that takes a filename, use the one that - // takes a file descriptor since wxWidgets can open a file with a Unicode name and - // libsndfile can't (under Windows). - sf.reset(SFCall(sf_open_fd, f.fd(), SFM_READ, &info, FALSE)); - } - - if (!sf) { - - memset(data, 0, SAMPLE_SIZE(format)*len); - - if (pAliasFile) { - // Set a marker to display an error message for the silence - auto hook = GetMissingAliasFileFound(); - if (hook) - hook( pAliasFile ); - } - } - } - mSilentLog = !sf; - - size_t framesRead = 0; - if (sf) { - auto seek_result = SFCall( - sf_seek, sf.get(), ( origin + start ).as_long_long(), SEEK_SET); - - if (seek_result < 0) - // error - ; - else { - auto channels = info.channels; - wxASSERT(channels >= 1); - wxASSERT((int)channel < channels); - - if (channels == 1 && - format == int16Sample && - sf_subtype_is_integer(info.format)) { - // If both the src and dest formats are integer formats, - // read integers directly from the file, conversions not needed - framesRead = SFCall( - sf_readf_short, sf.get(), (short *)data, len); - } - else if (channels == 1 && - format == int24Sample && - sf_subtype_is_integer(info.format)) { - framesRead = SFCall( - sf_readf_int, sf.get(), (int *)data, len); - - // libsndfile gave us the 3 byte sample in the 3 most - // significant bytes -- we want it in the 3 least - // significant bytes. - int *intPtr = (int *)data; - for( size_t i = 0; i < framesRead; i++ ) - intPtr[i] = intPtr[i] >> 8; - } - else if (format == int16Sample && - !sf_subtype_more_than_16_bits(info.format)) { - // Special case: if the file is in 16-bit (or less) format, - // and the calling method wants 16-bit data, go ahead and - // read 16-bit data directly. This is a pretty common - // case, as most audio files are 16-bit. - SampleBuffer buffer(len * channels, int16Sample); - framesRead = SFCall( - sf_readf_short, sf.get(), (short *)buffer.ptr(), len); - for (size_t i = 0; i < framesRead; i++) - ((short *)data)[i] = - ((short *)buffer.ptr())[(channels * i) + channel]; - } - else { - // Otherwise, let libsndfile handle the conversion and - // scaling, and pass us normalized data as floats. We can - // then convert to whatever format we want. - SampleBuffer buffer(len * channels, floatSample); - framesRead = SFCall( - sf_readf_float, sf.get(), (float *)buffer.ptr(), len); - auto bufferPtr = (samplePtr)((float *)buffer.ptr() + channel); - CopySamples(bufferPtr, floatSample, - (samplePtr)data, format, - framesRead, - true /* high quality by default */, - channels /* source stride */); - } - } - } - - if ( framesRead < len ) { - if (mayThrow) - throw FileException{ FileException::Cause::Read, fileName }; - ClearSamples(data, format, framesRead, len - framesRead); - } - - return framesRead; -} - -/// Constructs an AliasBlockFile based on the given information about -/// the aliased file. -/// -/// Note that derived classes /must/ call AliasBlockFile::WriteSummary() -/// in their constructors for the summary file to be correctly written -/// to disk. -/// -/// @param baseFileName The name of the summary file to be written, but -/// without an extension. This constructor will add -/// the appropriate extension before passing it to -/// BlockFile::BlockFile -/// @param aliasedFileName The name of the file where the audio data for -/// this block actually exists. -/// @param aliasStart The offset in the aliased file where this block's -/// data begins -/// @param aliasLen The length of this block's data in the aliased -/// file. -/// @param aliasChannel The channel where this block's data is located in -/// the aliased file -AliasBlockFile::AliasBlockFile(wxFileNameWrapper &&baseFileName, - wxFileNameWrapper &&aliasedFileName, - sampleCount aliasStart, - size_t aliasLen, int aliasChannel): - BlockFile { - (baseFileName.SetExt(wxT("auf")), std::move(baseFileName)), - aliasLen - }, - mAliasedFileName(std::move(aliasedFileName)), - mAliasStart(aliasStart), - mAliasChannel(aliasChannel) -{ - mSilentAliasLog=FALSE; -} - -AliasBlockFile::AliasBlockFile(wxFileNameWrapper &&existingSummaryFileName, - wxFileNameWrapper &&aliasedFileName, - sampleCount aliasStart, - size_t aliasLen, - int aliasChannel, - float min, float max, float rms): - BlockFile{ std::move(existingSummaryFileName), aliasLen }, - mAliasedFileName(std::move(aliasedFileName)), - mAliasStart(aliasStart), - mAliasChannel(aliasChannel) -{ - mMin = min; - mMax = max; - mRMS = rms; - mSilentAliasLog=FALSE; -} -/// Write the summary to disk. Derived classes must call this method -/// from their constructors for the summary to be correctly written. -/// It uses the derived class's ReadData() to retrieve the data to -/// summarize. -void AliasBlockFile::WriteSummary() -{ - // To build the summary data, call ReadData (implemented by the - // derived classes) to get the sample data - // Call this first, so that in case of exceptions from ReadData, there is - // no NEW output file - SampleBuffer sampleData(mLen, floatSample); - this->ReadData(sampleData.ptr(), floatSample, 0, mLen); - - // Now checked carefully in the DirManager - //wxASSERT( !wxFileExists(FILENAME(mFileName.GetFullPath()))); - - // I would much rather have this code as part of the constructor, but - // I can't call virtual functions from the constructor. So we just - // need to ensure that every derived class calls this in *its* constructor - wxFFile summaryFile(mFileName.GetFullPath(), wxT("wb")); - - if( !summaryFile.IsOpened() ){ - // Never silence the Log w.r.t write errors; they always count - // as NEW errors - wxLogError(wxT("Unable to write summary data to file %s"), - mFileName.GetFullPath()); - // If we can't write, there's nothing to do. - return; - } - - ArrayOf cleanup; - void *summaryData = BlockFile::CalcSummary(sampleData.ptr(), mLen, - floatSample, cleanup); - summaryFile.Write(summaryData, mSummaryInfo.totalSummaryBytes); -} - -AliasBlockFile::~AliasBlockFile() -{ -} - -const wxFileNameWrapper &AliasBlockFile::GetExternalFileName() const -{ - return GetAliasedFileName(); -} - -void AliasBlockFile::SetExternalFileName( wxFileNameWrapper &&newName ) -{ - ChangeAliasedFileName( std::move( newName ) ); -} - -/// Read the summary of this alias block from disk. Since the audio data -/// is elsewhere, this consists of reading the entire summary file. -/// Fill with zeroes and return false if data are unavailable for any reason. -/// -/// @param *data The buffer where the summary data will be stored. It must -/// be at least mSummaryInfo.totalSummaryBytes long. -bool AliasBlockFile::ReadSummary(ArrayOf &data) -{ - data.reinit( mSummaryInfo.totalSummaryBytes ); - wxFFile summaryFile(mFileName.GetFullPath(), wxT("rb")); - - { - Optional silence{}; - if (mSilentLog) - silence.emplace(); - - if (!summaryFile.IsOpened()){ - - // NEW model; we need to return valid data - memset(data.get(), 0, mSummaryInfo.totalSummaryBytes); - - // we silence the logging for this operation in this object - // after first occurrence of error; it's already reported and - // spewing at the user will complicate the user's ability to - // deal - mSilentLog = TRUE; - return false; - - } - else mSilentLog = FALSE; // worked properly, any future error is NEW - } - - auto read = summaryFile.Read(data.get(), mSummaryInfo.totalSummaryBytes); - if (read != mSummaryInfo.totalSummaryBytes) { - memset(data.get(), 0, mSummaryInfo.totalSummaryBytes); - return false; - } - - FixSummary(data.get()); - - return true; -} - -/// Modify this block to point at a different file. This is generally -/// looked down on, but it is necessary in one case: see -/// DirManager::EnsureSafeFilename(). -void AliasBlockFile::ChangeAliasedFileName(wxFileNameWrapper &&newAliasedFile) -{ - mAliasedFileName = std::move(newAliasedFile); -} - -auto AliasBlockFile::GetSpaceUsage() const -> DiskByteCount -{ - wxFFile summaryFile(mFileName.GetFullPath()); - return summaryFile.Length(); -} - diff --git a/src/BlockFile.h b/src/BlockFile.h deleted file mode 100644 index 4eb5a5e58..000000000 --- a/src/BlockFile.h +++ /dev/null @@ -1,287 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - BlockFile.h - - Joshua Haberman - Dominic Mazzoni - -**********************************************************************/ - -#ifndef __AUDACITY_BLOCKFILE__ -#define __AUDACITY_BLOCKFILE__ - -#include "SampleFormat.h" - -#include "wxFileNameWrapper.h" // member variable - -#include - -class XMLWriter; - -class SummaryInfo { - public: - SummaryInfo(size_t samples); - - int fields; /* Usually 3 for Min, Max, RMS */ - sampleFormat format; - int bytesPerFrame; - size_t frames64K; - int offset64K; - size_t frames256; - int offset256; - size_t totalSummaryBytes; -}; - - - -class BlockFile; -class AliasBlockFile; -using BlockFilePtr = std::shared_ptr; - -template< typename Result, typename... Args > -inline std::shared_ptr< Result > make_blockfile (Args && ... args) -{ - return std::make_shared< Result > ( std::forward< Args > ( args )... ); -} - -class PROFILE_DLL_API BlockFile /* not final, abstract */ { - public: - - // Type of function to be called when opening of an alias block file for read - // discovers that the other audio file it depends on is absent - using MissingAliasFileFoundHook = - std::function< void(const AliasBlockFile*) >; - // Install a hook, and return the previous hook - static MissingAliasFileFoundHook - SetMissingAliasFileFound( MissingAliasFileFoundHook hook ); - - // Constructor / Destructor - - /// Construct a BlockFile. - BlockFile(wxFileNameWrapper &&fileName, size_t samples); - virtual ~BlockFile(); - - static unsigned long gBlockFileDestructionCount; - - // Reading - - /// Retrieves audio data from this BlockFile - /// Returns the number of samples really read, not more than len - /// If fewer can be read than len, throws an exception if mayThrow is true, - /// otherwise fills the remainder of data with zeroes. - virtual size_t ReadData(samplePtr data, sampleFormat format, - size_t start, size_t len, bool mayThrow = true) - const = 0; - - // Other Properties - - /// Stores a representation of this file in XML - virtual void SaveXML(XMLWriter &xmlFile) = 0; - - /// Gets the filename of the disk file associated with this BlockFile - /// (can be empty -- some BlockFiles, like SilentBlockFile, correspond to - /// no file on disk) - /// Avoids copying wxFileName by returning a reference, but for some subclasses - /// of BlockFile, you must exclude other threads from changing the name so long - /// as you have only a reference. Thus, this wrapper object that guarantees release - /// of any lock when it goes out of scope. Call mLocker.reset() to unlock it sooner. - struct GetFileNameResult { - const wxFileName &name; - - GetFileNameResult(const wxFileName &name_) - : name{ name_ } {} - - GetFileNameResult(const GetFileNameResult&) PROHIBITED; - GetFileNameResult &operator= (const GetFileNameResult&) PROHIBITED; - - GetFileNameResult(GetFileNameResult &&that) - : name{ that.name } {} - }; - virtual GetFileNameResult GetFileName() const; - virtual void SetFileName(wxFileNameWrapper &&name); - - // Managing an external file dependency - // Default always returns empty - virtual const wxFileNameWrapper &GetExternalFileName() const; - // Default does nothing (and gives assertion violation in debug) - virtual void SetExternalFileName( wxFileNameWrapper &&newName ); - - size_t GetLength() const { return mLen; } - void SetLength(size_t newLen) { mLen = newLen; } - - /// Locks this BlockFile, to prevent it from being moved - virtual void Lock(); - /// Unlock this BlockFile, allowing it to be moved - virtual void Unlock(); - /// Returns TRUE if this BlockFile is locked - virtual bool IsLocked(); - - struct MinMaxRMS { float min, max, RMS; }; - - /// Gets extreme values for the specified region - virtual MinMaxRMS GetMinMaxRMS(size_t start, size_t len, - bool mayThrow = true) const; - /// Gets extreme values for the entire block - virtual MinMaxRMS GetMinMaxRMS(bool mayThrow = true) const; - /// Returns the 256 byte summary data block - virtual bool Read256(float *buffer, size_t start, size_t len); - /// Returns the 64K summary data block - virtual bool Read64K(float *buffer, size_t start, size_t len); - - /// Returns TRUE if this block references another disk file - virtual bool IsAlias() const { return false; } - - /// Create a NEW BlockFile identical to this, using the given filename - virtual BlockFilePtr Copy(wxFileNameWrapper &&newFileName) = 0; - - // Report disk space usage. - using DiskByteCount = unsigned long long; - virtual DiskByteCount GetSpaceUsage() const = 0; - - /// if the on-disk state disappeared, either recover it (if it was - //summary only), write out a placeholder of silence data (missing - //.au) or mark the blockfile to deal some other way without spewing - //errors. - // May throw exceptions for i/o errors. - virtual void Recover() = 0; - /// if we've detected an on-disk problem, the user opted to - //continue and the error persists, don't keep reporting it. The - //Object implements this functionality internally, but we want to - //be able to tell the logging to shut up from outside too. - void SilenceLog() const { mSilentLog = TRUE; } - - ///when the project closes, it locks the blockfiles. - ///Override this in case it needs special treatment. - // not balanced by unlocking calls. - virtual void CloseLock(){Lock();} - - protected: - /// Prevents a read on other threads. The basic blockfile runs on only one thread, so does nothing. - virtual void LockRead() const {} - /// Allows reading on other threads. - virtual void UnlockRead() const {} - - struct ReadLocker { void operator () ( const BlockFile *p ) const { - if (p) p->LockRead(); } }; - struct ReadUnlocker { void operator () ( const BlockFile *p ) const { - if (p) p->UnlockRead(); } }; - using ReadLockBase = - std::unique_ptr< const BlockFile, ReadUnlocker >; - - public: - class ReadLock : public ReadLockBase - { - friend BlockFile; - ReadLock ( const BlockFile *p, const BlockFile::ReadUnlocker &u ) - : ReadLockBase { p, u } {} - public: - ReadLock(ReadLock&&that) : ReadLockBase{ std::move(that) } {} - using Suspension = std::unique_ptr< const BlockFile, ReadLocker >; - Suspension Suspend() const - { if (get()) get()->UnlockRead(); - return Suspension{ get(), ReadLocker{} }; } - }; - - // RAII wrapper about the read locking functions - ReadLock LockForRead() const { LockRead(); return { this, ReadUnlocker{} }; } - - private: - - protected: - /// Calculate summary data for the given sample data - /// Overrides have differing details of memory management - virtual void *CalcSummary(samplePtr buffer, size_t len, - sampleFormat format, - // This gets filled, if the caller needs to deallocate. Else it is null. - ArrayOf &cleanup); - // Common, nonvirtual calculation routine for the use of the above - void CalcSummaryFromBuffer(const float *fbuffer, size_t len, - float *summary256, float *summary64K); - - /// Read the summary section of the file. Derived classes implement. - virtual bool ReadSummary(ArrayOf &data) = 0; - - /// Byte-swap the summary data, in case it was saved by a system - /// on a different platform - virtual void FixSummary(void *data); - - static size_t CommonReadData( - bool mayThrow, - const wxFileName &fileName, bool &mSilentLog, - const AliasBlockFile *pAliasFile, sampleCount origin, unsigned channel, - samplePtr data, sampleFormat format, size_t start, size_t len, - const sampleFormat *pLegacyFormat = nullptr, size_t legacyLen = 0); - - private: - int mLockCount; - - static ArrayOf fullSummary; - - protected: - wxFileNameWrapper mFileName; - size_t mLen; - SummaryInfo mSummaryInfo; - float mMin, mMax, mRMS; - mutable bool mSilentLog; -}; - -/// A BlockFile that refers to data in an existing file - -/// An AliasBlockFile references an existing disk file for its storage -/// instead of copying the data. It still writes a file to disk, but -/// only stores summary data in it. -/// -/// This is a common base class for all alias block files. It handles -/// reading and writing summary data, leaving very little for derived -/// classes to need to implement. -class AliasBlockFile /* not final */ : public BlockFile -{ - public: - - // Constructor / Destructor - - /// Constructs an AliasBlockFile - AliasBlockFile(wxFileNameWrapper &&baseFileName, - wxFileNameWrapper &&aliasedFileName, sampleCount aliasStart, - size_t aliasLen, int aliasChannel); - AliasBlockFile(wxFileNameWrapper &&existingSummaryFileName, - wxFileNameWrapper &&aliasedFileName, sampleCount aliasStart, - size_t aliasLen, int aliasChannel, - float min, float max, float RMS); - virtual ~AliasBlockFile(); - - // Reading - - DiskByteCount GetSpaceUsage() const override; - - /// as SilentLog (which would affect Summary data access), but - // applying to Alias file access - void SilenceAliasLog() const { mSilentAliasLog = TRUE; } - - // - // These methods are for advanced use only! - // - const wxFileNameWrapper &GetAliasedFileName() const { return mAliasedFileName; } - void ChangeAliasedFileName(wxFileNameWrapper &&newAliasedFile); - bool IsAlias() const override { return true; } - - const wxFileNameWrapper &GetExternalFileName() const override; - void SetExternalFileName( wxFileNameWrapper &&newName ) override; - - protected: - // Introduce a NEW virtual. - /// Write the summary to disk, using the derived ReadData() to get the data - virtual void WriteSummary(); - /// Read the summary into a buffer - bool ReadSummary(ArrayOf &data) override; - - wxFileNameWrapper mAliasedFileName; - sampleCount mAliasStart; - const int mAliasChannel; - mutable bool mSilentAliasLog; -}; - -#endif - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81d18807b..25fb00b6e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,8 +96,6 @@ list( APPEND SOURCES BatchProcessDialog.h Benchmark.cpp Benchmark.h - BlockFile.cpp - BlockFile.h CellularPanel.cpp CellularPanel.h ClassicThemeAsCeeCode.h @@ -110,16 +108,12 @@ list( APPEND SOURCES CrashReport.cpp CrashReport.h DarkThemeAsCeeCode.h - Dependencies.cpp - Dependencies.h DeviceChange.cpp DeviceChange.h DeviceManager.cpp DeviceManager.h Diags.cpp Diags.h - DirManager.cpp - DirManager.h Dither.cpp Dither.h Envelope.cpp @@ -178,8 +172,6 @@ list( APPEND SOURCES MemoryX.h Menus.cpp Menus.h - MissingAliasFileDialog.cpp - MissingAliasFileDialog.h Mix.cpp Mix.h MixerBoard.cpp @@ -199,16 +191,14 @@ list( APPEND SOURCES Prefs.h Printing.cpp Printing.h -# Profiler.cpp -# Profiler.h Project.cpp Project.h ProjectAudioIO.cpp ProjectAudioIO.h ProjectAudioManager.cpp ProjectAudioManager.h - ProjectFSCK.cpp - ProjectFSCK.h +# ProjectFSCK.cpp +# ProjectFSCK.h ProjectFileIO.cpp ProjectFileIO.h ProjectFileIORegistry.cpp @@ -241,6 +231,8 @@ list( APPEND SOURCES Resample.h RingBuffer.cpp RingBuffer.h + SampleBlock.cpp + SampleBlock.h SampleFormat.cpp SampleFormat.h Screenshot.cpp @@ -329,19 +321,6 @@ list( APPEND SOURCES float_cast.h wxFileNameWrapper.h - # Blockfile - - blockfile/LegacyAliasBlockFile.cpp - blockfile/LegacyAliasBlockFile.h - blockfile/LegacyBlockFile.cpp - blockfile/LegacyBlockFile.h - blockfile/PCMAliasBlockFile.cpp - blockfile/PCMAliasBlockFile.h - blockfile/SilentBlockFile.cpp - blockfile/SilentBlockFile.h - blockfile/SimpleBlockFile.cpp - blockfile/SimpleBlockFile.h - # Commands commands/AppCommandEvent.cpp diff --git a/src/CommonCommandFlags.cpp b/src/CommonCommandFlags.cpp index 0cc74b69d..aca967d44 100644 --- a/src/CommonCommandFlags.cpp +++ b/src/CommonCommandFlags.cpp @@ -233,7 +233,7 @@ const ReservedCommandFlag& return undoManager.UnsavedChanges() || - !ProjectFileIO::Get( project ).IsProjectSaved() + ProjectFileIO::Get( project ).IsModified() ; } }; return flag; } diff --git a/src/DirManager.cpp b/src/DirManager.cpp deleted file mode 100644 index 4dc815828..000000000 --- a/src/DirManager.cpp +++ /dev/null @@ -1,1691 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - Audacity(R) is copyright (c) 1999-2008 Audacity Team. - License: GPL v2. See License.txt. - - DirManager.cpp - - Dominic Mazzoni - Matt Brubeck - Michael Chinen - James Crook - Al Dimond - Brian Gunlogson - Josh Haberman - Vaughan Johnson - Leland Lucius - Monty - Markus Meyer - -*******************************************************************//*! - -\class DirManager -\brief Creates and manages BlockFile objects. - - This class manages the files that a project uses to store most - of its data. It creates NEW BlockFile objects, which can - be used to store any type of data. BlockFiles support all of - the common file operations, but they also support reference - counting, so two different parts of a project can point to - the same block of data. - - For example, a track might contain 10 blocks of data representing - its audio. If you copy the last 5 blocks and paste at the - end of the file, no NEW blocks need to be created - we just store - pointers to NEW ones. When part of a track is deleted, the - affected blocks decrement their reference counts, and when they - reach zero they are deleted. This same mechanism is also used - to implement Undo. - - The DirManager, besides mapping filenames to absolute paths, - also hashes all of the block names used in a project, so that - when reading a project from disk, multiple copies of the - same block still get mapped to the same BlockFile object. - - The blockfile/directory scheme is rather complicated with two different schemes. - The current scheme uses two levels of subdirectories - up to 256 'eXX' and up to - 256 'dYY' directories within each of the 'eXX' dirs, where XX and YY are hex chars. - In each of the dXX directories there are up to 256 audio files (e.g. .au or .auf). - They have a filename scheme of 'eXXYYZZZZ', where XX and YY refers to the - subdirectories as above. The 'ZZZZ' component is generated randomly for some reason. - The XX and YY components are sequential. - DirManager fills up the current dYY subdir until 256 are created, and moves on to the next one. - - So for example, the first blockfile created may be 'e00/d00/e0000a23b.au' and the next - 'e00/d00/e000015e8.au', and the 257th may be 'e00/d01/e0001f02a.au'. - On close the blockfiles that are no longer referenced by the project (edited or deleted) are removed, - along with the consequent empty directories. - - -*//*******************************************************************/ - - -#include "Audacity.h" // for __UNIX__ -#include "DirManager.h" - -#include // to use time() for srand() - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// chmod -#ifdef __UNIX__ -#include -#include -#endif - -#include "BlockFile.h" -#include "FileNames.h" -#include "InconsistencyException.h" -#include "Prefs.h" -#include "Project.h" -#include "widgets/Warning.h" -#include "widgets/AudacityMessageBox.h" -#include "widgets/ProgressDialog.h" - -#if defined(__WXMAC__) -#include -#include -#endif - - -wxMemorySize GetFreeMemory() -{ - wxMemorySize avail; - -#if defined(__WXMAC__) - mach_port_t port = mach_host_self(); - mach_msg_type_number_t cnt = HOST_VM_INFO_COUNT; - vm_statistics_data_t stats; - vm_size_t pagesize = 0; - - memset(&stats, 0, sizeof(stats)); - - host_page_size(port, &pagesize); - host_statistics(port, HOST_VM_INFO, (host_info_t) &stats, &cnt); - avail = stats.free_count * pagesize; -#else - avail = wxGetFreeMemory(); -#endif - - return avail; -} - -// -// local helper functions for subdirectory traversal -// - -// Behavior of RecursivelyEnumerate is tailored to our uses and not -// entirely straightforward. We use it only for recursing into -// Audacity projects, but beware that it may be applied to a directory -// that contains other things too, for example a temp directory. -// It recurses depth-first from the passed- -// in directory into its subdirs according to optional dirspec -// pattern, building a list of directories and (optionally) files -// in the listed order. -// The dirspec is not applied to subdirs of subdirs. -// The filespec is applied to all files in subdirectories. -// Files in the passed-in directory will not be -// enumerated. Also, the passed-in directory is the last entry added -// to the list. -// JKC: Using flag wxDIR_NO_FOLLOW to NOT follow symbolic links. -// Directories and files inside a project should never be symbolic -// links, so if we find one, do not follow it. -int DirManager::RecursivelyEnumerate(const FilePath &dirPath, - FilePaths& filePathArray, // output: all files in dirPath tree - wxString dirspec, - wxString filespec, - bool bFiles, bool bDirs, - int progress_count, - int progress_bias, - ProgressDialog* progress) -{ - int count=0; - bool cont; - - wxDir dir(dirPath); - if(dir.IsOpened()){ - wxString name; - - // Don't DELETE files from a selective top level, e.g. if handed "projects*" as the - // directory specifier. - if (bFiles && dirspec.empty() ){ - cont= dir.GetFirst(&name, filespec, wxDIR_FILES | wxDIR_HIDDEN | wxDIR_NO_FOLLOW); - while ( cont ){ - FilePath filepath = dirPath + wxFILE_SEP_PATH + name; - - count++; - filePathArray.push_back(filepath); - - cont = dir.GetNext(&name); - - if (progress) - progress->Update(count + progress_bias, - progress_count); - } - } - - cont= dir.GetFirst(&name, dirspec, wxDIR_DIRS | wxDIR_NO_FOLLOW); - while ( cont ){ - FilePath subdirPath = dirPath + wxFILE_SEP_PATH + name; - count += RecursivelyEnumerate( - subdirPath, filePathArray, wxEmptyString,filespec, - bFiles, bDirs, - progress_count, count + progress_bias, - progress); - cont = dir.GetNext(&name); - } - } - - if (bDirs) { - filePathArray.push_back(dirPath); - count++; - } - - return count; -} - -int DirManager::RecursivelyEnumerateWithProgress(const FilePath &dirPath, - FilePaths& filePathArray, // output: all files in dirPath tree - wxString dirspec, - wxString filespec, - bool bFiles, bool bDirs, - int progress_count, - const TranslatableString &message) -{ - Optional progress{}; - - if (!message.empty()) - progress.emplace( XO("Progress"), message ); - - int count = RecursivelyEnumerate( - dirPath, filePathArray, dirspec,filespec, - bFiles, bDirs, - progress_count, 0, - progress ? &*progress : nullptr); - - return count; -} - -int DirManager::RecursivelyCountSubdirs( const FilePath &dirPath ) -{ - bool bContinue; - int nCount = 0; - wxDir dir(dirPath); - if (dir.IsOpened() && dir.HasSubDirs()) - { - wxString name; - bContinue = dir.GetFirst(&name, wxEmptyString, wxDIR_DIRS); - while (bContinue) - { - nCount++; - FilePath subdirPath = dirPath + wxFILE_SEP_PATH + name; - nCount += RecursivelyCountSubdirs(subdirPath); - bContinue = dir.GetNext(&name); - } - } - return nCount; -} - -int DirManager::RecursivelyRemoveEmptyDirs(const FilePath &dirPath, - int nDirCount, - ProgressDialog* pProgress) -{ - bool bContinue; - wxDir dir(dirPath); - int nCount = 0; - if (dir.IsOpened()) - { - if (dir.HasSubDirs()) - { - wxString name; - bContinue = dir.GetFirst(&name, wxEmptyString, wxDIR_DIRS); - while (bContinue) - { - FilePath subdirPath = dirPath + wxFILE_SEP_PATH + name; - nCount += RecursivelyRemoveEmptyDirs(subdirPath, nDirCount, pProgress); - bContinue = dir.GetNext(&name); - } - } - // Have to recheck dir.HasSubDirs() again, in case they all were deleted in recursive calls. - if (!dir.HasSubDirs() && !dir.HasFiles() && (dirPath.Right(5) != wxT("_data"))) - { - // No subdirs or files. It's empty so DELETE it. - // Vaughan, 2010-07-07: - // Note that, per http://src.chromium.org/svn/trunk/src/base/file_util_win.cc, among others, - // "Some versions of Windows return ERROR_FILE_NOT_FOUND (0x2) when deleting - // an empty directory..." Supposedly fixed in Vista and up. - // I discovered this on WinXP. I tried several other Windows SDK functions (e.g., _rmdir - // and RemoveDirectory), and they all give same results. - // I noticed dirs get deleted in RecursivelyRemove, maybe because it doesn't - // consider whether the path is a directory or a file and wxRemoveFile()'s it first. - // Tried it here on WinXP, but no joy. Leave the code in case it works on other Win systems. - #ifdef __WXMSW__ - ::wxRemoveFile(dirPath); - #endif - ::wxRmdir(dirPath); - } - nCount++; // Count dirPath in progress. - if (pProgress) - pProgress->Update(nCount, nDirCount); - } - return nCount; -} - -void DirManager::RecursivelyRemove(const FilePaths& filePathArray, int count, int bias, - int flags, const TranslatableString &message) -{ - bool bFiles= (flags & kCleanFiles) != 0; - bool bDirs = (flags & kCleanDirs) != 0; - bool bDirsMustBeEmpty = (flags & kCleanDirsOnlyIfEmpty) != 0; - Optional progress{}; - - - if (!message.empty()) - progress.emplace( XO("Progress"), message ); - - auto nn = filePathArray.size(); - for ( size_t ii = 0; ii < nn; ++ii ) { - const auto &file = filePathArray[ ii ]; - if (bFiles) - ::wxRemoveFile(file); - if (bDirs) { - // continue will go to the next item, and skip - // attempting to delete the directory. - if( bDirsMustBeEmpty ){ - wxDir dir( file ); - if( !dir.IsOpened() ) - continue; - if( dir.HasFiles() ) - continue; - if( dir.HasSubDirs() ) - continue; - } - -#ifdef __WXMSW__ - if (!bFiles) - ::wxRemoveFile(file); // See note above about wxRmdir sometimes incorrectly failing on Windows. -#endif - - if (! ::wxRmdir(file) ) { - wxDir dir(file); - if(dir.IsOpened()) { - wxLogMessage(file + wxString(" still contains:")); - wxString name; - auto cont = dir.GetFirst(&name); - while ( cont ) { - wxLogMessage(file + wxString(wxFILE_SEP_PATH) + name ); - cont = dir.GetNext(&name); - } - } - else - wxLogMessage(wxString("Can't enumerate directory ") + file); - } - } - if (progress) - progress->Update((int) ii + bias, count); - } -} - - -// -// DirManager -// - -// Static class variables -wxString DirManager::globaltemp; -int DirManager::numDirManagers = 0; -bool DirManager::dontDeleteTempFiles = false; - -namespace { - -// Global tracking of all outstanding DirManagers -std::vector< std::weak_ptr< DirManager > > sDirManagers; - -} - -std::shared_ptr DirManager::Create() -{ - auto result = std::shared_ptr< DirManager >( safenew DirManager ); - sDirManagers.push_back( result ); - return result; -} - -static const AudacityProject::AttachedObjects::RegisteredFactory key{ - [](AudacityProject&) { return DirManager::Create(); } -}; - -DirManager &DirManager::Get( AudacityProject &project ) -{ - return project.AttachedObjects::Get< DirManager >( key ); -} - -const DirManager &DirManager::Get( const AudacityProject &project ) -{ - return Get( const_cast< AudacityProject & >( project ) ); -} - -DirManager &DirManager::Reset( AudacityProject &project ) -{ - auto dirManager = DirManager::Create(); - project.AttachedObjects::Assign( key, dirManager ); - return *dirManager; -} - -void DirManager::Destroy( AudacityProject &project ) -{ - project.AttachedObjects::Assign( key, nullptr ); -} - -DirManager::DirManager() -{ - wxLogDebug(wxT("DirManager: Created new instance.")); - - mLastBlockFileDestructionCount = BlockFile::gBlockFileDestructionCount; - - // Seed the random number generator. - // this need not be strictly uniform or random, but it should give - // unclustered numbers - srand(time(NULL)); - - // Set up local temp subdir - // Previously, Audacity just named project temp directories "project0", - // "project1" and so on. But with the advent of recovery code, we need a - // unique name even after a crash. So we create a random project index - // and make sure it is not used already. This will not pose any performance - // penalties as long as the number of open Audacity projects is much - // lower than RAND_MAX. - do { - mytemp = globaltemp + wxFILE_SEP_PATH + - wxString::Format(wxT("project%d"), rand()); - } while (wxDirExists(mytemp)); - - numDirManagers++; - - projPath = wxT(""); - projName = wxT(""); - - mMaxSamples = ~size_t(0); - - // toplevel pool hash is fully populated to begin - { - // We can bypass the accessor function while initializing - auto &balanceInfo = mBalanceInfo; - auto &dirTopPool = balanceInfo.dirTopPool; - for(int i = 0; i < 256; ++i) - dirTopPool[i] = 0; - } - - // Make sure there is plenty of space for temp files - wxLongLong freeSpace = 0; - if (wxGetDiskSpace(globaltemp, NULL, &freeSpace)) { - if (freeSpace < wxLongLong(wxLL(100 * 1048576))) { - ShowWarningDialog(NULL, wxT("DiskSpaceWarning"), - XO("There is very little free disk space left on this volume.\nPlease select another temporary directory in Preferences.")); - } - } -} - -DirManager::~DirManager() -{ - auto start = sDirManagers.begin(), finish = sDirManagers.end(), - iter = std::remove_if( start, finish, - [=]( const std::weak_ptr &ptr ){ - return ptr.expired() || ptr.lock().get() == this; - } ); - sDirManagers.erase( iter, finish ); - - numDirManagers--; - if (numDirManagers == 0) { - CleanTempDir(); - //::wxRmdir(temp); - } else if( projFull.empty() && !mytemp.empty()) { - CleanDir(mytemp, wxEmptyString, ".DS_Store", XO("Cleaning project temporary files"), kCleanTopDirToo | kCleanDirsOnlyIfEmpty ); - } -} - - -// static -// This is quite a dangerous function. In the temp dir it will DELETE every directory -// recursively, that has 'project*' as the name - EVEN if it happens not to be an Audacity -// project but just something else called project. -void DirManager::CleanTempDir() -{ - // with default flags (none) this does not clean the top directory, and may remove non-empty - // directories. - CleanDir(globaltemp, wxT("project*"), wxEmptyString, XO("Cleaning up temporary files")); -} - -// static -void DirManager::CleanDir( - const FilePath &path, - const wxString &dirSpec, - const wxString &fileSpec, - const TranslatableString &msg, - int flags) -{ - if (dontDeleteTempFiles) - return; // do nothing - - FilePaths filePathArray, dirPathArray; - - int countFiles = - RecursivelyEnumerate(path, filePathArray, dirSpec, fileSpec, true, false); - int countDirs = - RecursivelyEnumerate(path, dirPathArray, dirSpec, fileSpec, false, true); - - // Subtract 1 because we don't want to DELETE the global temp directory, - // which this will find and list last. - if ((flags & kCleanTopDirToo)==0) { - // Remove the globaltemp itself from the array so that it is not deleted. - --countDirs; - dirPathArray.resize(countDirs); - } - - auto count = countFiles + countDirs; - if (count == 0) - return; - - RecursivelyRemove(filePathArray, count, 0, flags | kCleanFiles, msg); - RecursivelyRemove(dirPathArray, count, countFiles, flags | kCleanDirs, msg); -} - -namespace { - struct PathRestorer { - PathRestorer( - bool &commitFlag, FilePath &path, FilePath &name, FilePath &full ) - : committed( commitFlag ) - - , projPath( path ) - , projName( name ) - , projFull( full ) - - , oldPath( path ) - , oldName( name ) - , oldFull( full ) - {} - - ~PathRestorer() - { - if (!committed) - projFull = oldFull, projName = oldName, projPath = oldPath; - } - - bool &committed; - FilePath &projPath, &projName, &projFull; - const FilePath oldPath, oldName, oldFull; - }; - - struct DirCleaner { - DirCleaner( bool &commitFlag, const FilePath &path ) - : committed( commitFlag ) - , fullPath( path ) - {} - ~DirCleaner() - { - if (!committed) - DirManager::CleanDir( - fullPath, - wxEmptyString, - wxEmptyString, - XO("Cleaning up after failed save"), - kCleanTopDirToo); - } - - bool &committed; - FilePath fullPath; - }; -} - -struct DirManager::ProjectSetter::Impl -{ - Impl( - DirManager &dm, - FilePath& newProjPath, const FilePath& newProjName, const bool bCreate, - bool moving ); - - void Commit(); - - DirManager &dirManager; - bool committed{ false }; - - // RAII object - // Save old state of paths in case of failure - PathRestorer pathRestorer{ - committed, - dirManager.projPath, dirManager.projName, dirManager.projFull }; - - // Another RAII object - // Be prepared to un-create directory on failure - Optional dirCleaner; - - // State variables to carry over into Commit() - // Remember old path to be cleaned up in case of successful move - FilePath oldFull{ dirManager.projFull }; - FilePaths newPaths; - size_t trueTotal{ 0 }; - bool moving{ true }; - - // Make this true only after successful construction - bool ok{ false }; -}; - -DirManager::ProjectSetter::ProjectSetter( - DirManager &dirManager, - FilePath& newProjPath, const FilePath& newProjName, const bool bCreate, - bool moving ) - : mpImpl{ - std::make_unique( dirManager, newProjPath, newProjName, bCreate, - moving ) - } -{ - -} - -DirManager::ProjectSetter::~ProjectSetter() -{ -} - -bool DirManager::ProjectSetter::Ok() -{ - return mpImpl->ok; -} - - -void DirManager::ProjectSetter::Commit() -{ - mpImpl->Commit(); -} - -DirManager::ProjectSetter::Impl::Impl( - DirManager &dm, - FilePath& newProjPath, const FilePath& newProjName, const bool bCreate, - bool moving_ ) -: dirManager{ dm } -, moving{ moving_ } -{ - // Choose new paths - if (newProjPath.empty()) - newProjPath = ::wxGetCwd(); - - dirManager.projPath = newProjPath; - dirManager.projName = newProjName; - if (newProjPath.Last() == wxFILE_SEP_PATH) - dirManager.projFull = newProjPath + newProjName; - else - dirManager.projFull = newProjPath + wxFILE_SEP_PATH + newProjName; - - // Verify new paths, maybe creating a directory - if (bCreate) { - if (!wxDirExists(dirManager.projFull) && - !wxMkdir(dirManager.projFull)) - return; - - #ifdef __UNIX__ - chmod(OSFILENAME(dirManager.projFull), 0775); - #endif - - #ifdef __WXMAC__ - chmod(OSFILENAME(dirManager.projFull), 0775); - #endif - - } - else if (!wxDirExists(dirManager.projFull)) - return; - - // Be prepared to un-create directory on failure - if (bCreate) - dirCleaner.emplace( committed, dirManager.projFull ); - - /* Hard-link or copy all files into this NEW directory. - - If any files are "locked" then all get copied. (This happens when - we perform a Save As - the files which belonged to the last - saved version of the old project must not be removed because of operations - on the NEW project, otherwise the old project would not be safe.) - - Copy also happens anyway when hard file links are not possible, as when - saving the project for the first time out of temporary storage and onto - some other storage device. - - Renaming would also be cheap like hard-linking, but linking is better - because it builds up a new tree of files without destroying anything in - the old tree. That destruction can be left to a commit phase which - proceeds only when all the building of the new tree has succeeded. - */ - - /* Bug2059: Don't deduce whether moving or copying just from the block - files, because an empty project, or one that was empty in its last saved - state, may have had no block files to lock. - Must treat as a copy if any is locked, but may also treat as copy if - the constructor argument tells us so. - With this change, the empty _data folder of the empty source project - will not be deleted in Commit(). - */ - - moving = moving && ! std::any_of( - dirManager.mBlockFileHash.begin(), dirManager.mBlockFileHash.end(), - []( const BlockHash::value_type &pair ){ - auto b = pair.second.lock(); - return b && b->IsLocked(); - } - ); - - trueTotal = 0; - - { - /* i18n-hint: This title appears on a dialog that indicates the progress - in doing something.*/ - ProgressDialog progress(XO("Progress"), - XO("Saving project data files")); - - int total = dirManager.mBlockFileHash.size(); - - bool link = moving; - for (const auto &pair : dirManager.mBlockFileHash) { - if( progress.Update((int) newPaths.size(), total) != ProgressResult::Success ) - return; - - FilePath newPath; - if (auto b = pair.second.lock()) { - auto result = - dirManager.LinkOrCopyToNewProjectDirectory( &*b, link ); - if (!result.first) - return; - newPath = result.second; - ++trueTotal; - } - newPaths.push_back( newPath ); - } - } - - ok = true; -} - -void DirManager::ProjectSetter::Impl::Commit() -{ - wxASSERT( ok ); - - // We have built all of the new file tree. - // So cancel the destructor actions of the RAII objects. - committed = true; - - auto size = newPaths.size(); - wxASSERT( size == dirManager.mBlockFileHash.size() ); - - // Commit changes to filenames in the BlockFile objects, and removal - // of files at old paths, ONLY NOW! This must be nothrow. - - // This copy-then-delete procedure is needed to make it safe to - // attempt save to another storage device, but fail. - - // It has the consequence that saving a project from one part of - // the device to another will not succeed unless there is sufficient - // space to hold originals and copies at the same time. Perhaps the - // extra cautions are not needed in that case, and the old procedure - // of renaming first, and reversing the renamings in case of failure, - // could still work safely. - - // But I don't know whether wxWidgets gives us a reliable means to - // distinguish that case. - - // I will err on the side of safety and simplicity and follow the - // same procedure in all cases. - - size_t ii = 0; - for (const auto &pair : dirManager.mBlockFileHash) - { - BlockFilePtr b = pair.second.lock(); - - if (b) { - if (moving || !b->IsLocked()) { - auto result = b->GetFileName(); - auto oldPath = result.name.GetFullPath(); - if (!oldPath.empty()) - wxRemoveFile( oldPath ); - } - - if (ii < size) - b->SetFileName( - wxFileNameWrapper{ wxFileName{ newPaths[ii] } } ); - } - - ++ii; - } - - // Some subtlety; SetProject is used both to move a temp project - // into a permanent home as well as just set up path variables when - // loading a project; in this latter case, the movement code does - // nothing because SetProject is called before there are any - // blockfiles. Cleanup code trigger is the same - - // Do the cleanup of the temporary directory only if not saving-as, which we - // detect by having done copies rather than moves. - if (moving && trueTotal > 0) { - // Clean up after ourselves; boldly remove all files and directories - // in the tree. (Unlike what the earlier version of this comment said.) - // Because this is a relocation of the project, not the case of closing - // a persistent project. - - // You may think the loops above guarantee that all files we put in the - // folders have been moved away already, but: - // to fix bug1567 on Mac, we need to find the extraneous .DS_Store files - // that we didn't put there, but that Finder may insert into the folders, - // and mercilessly remove them, in addition to removing the directories. - - auto cleanupLoc1 = oldFull.empty() ? dirManager.mytemp : oldFull; - CleanDir( - cleanupLoc1, - wxEmptyString, // EmptyString => ALL directories. - // If the next line were wxEmptyString, ALL files would be removed. - ".DS_Store", // Other project files should already have been removed. - XO("Cleaning up cache directories"), - kCleanTopDirToo); - } -} - -bool DirManager::SetProject( - FilePath& newProjPath, const FilePath& newProjName, const bool bCreate) -{ - ProjectSetter setter{ *this, newProjPath, newProjName, bCreate, true }; - if (!setter.Ok()) - return false; - setter.Commit(); - return true; -} - -FilePath DirManager::GetProjectDataDir() -{ - return projFull; -} - -FilePath DirManager::GetProjectName() -{ - return projName; -} - -wxLongLong DirManager::GetFreeDiskSpace() -{ - wxLongLong freeSpace = -1; - wxFileName path; - - path.SetPath(projPath.empty() ? mytemp : projPath); - - // Use the parent directory if the project directory hasn't yet been created - if (!path.DirExists()) - { - path.RemoveLastDir(); - } - - if (!wxGetDiskSpace(path.GetFullPath(), NULL, &freeSpace)) - { - freeSpace = -1; - } - - return freeSpace; -} - -FilePath DirManager::GetDataFilesDir() const -{ - return !projFull.empty()? projFull: mytemp; -} - -void DirManager::SetLocalTempDir(const wxString &path) -{ - mytemp = path; -} - -wxFileNameWrapper DirManager::MakeBlockFilePath(const wxString &value) { - - wxFileNameWrapper dir; - dir.AssignDir(GetDataFilesDir()); - - if(value.GetChar(0)==wxT('d')){ - // this file is located in a subdirectory tree - int location=value.Find(wxT('b')); - wxString subdir=value.Mid(0,location); - dir.AppendDir(subdir); - - if(!dir.DirExists()) - dir.Mkdir(); - } - - if(value.GetChar(0)==wxT('e')){ - // this file is located in a NEW style two-deep subdirectory tree - wxString topdir=value.Mid(0,3); - wxString middir=wxT("d"); - middir.Append(value.Mid(3,2)); - - dir.AppendDir(topdir); - dir.AppendDir(middir); - - if(!dir.DirExists() && !dir.Mkdir(0777,wxPATH_MKDIR_FULL)) - { // need braces to avoid compiler warning about ambiguous else, see the macro - wxLogSysError(wxT("mkdir in DirManager::MakeBlockFilePath failed.")); - } - } - return dir; -} - -bool DirManager::AssignFile(wxFileNameWrapper &fileName, - const wxString &value, - bool diskcheck) -{ - wxFileNameWrapper dir{ MakeBlockFilePath(value) }; - - if(diskcheck){ - // verify that there's no possible collision on disk. If there - // is, log the problem and return FALSE so that MakeBlockFileName - // can try again - - wxDir checkit(dir.GetFullPath()); - if(!checkit.IsOpened()) return FALSE; - - // this code is only valid if 'value' has no extension; that - // means, effectively, AssignFile may be called with 'diskcheck' - // set to true only if called from MakeFileBlockName(). - - wxString filespec; - filespec.Printf(wxT("%s.*"),value); - if(checkit.HasFiles(filespec)){ - // collision with on-disk state! - wxString collision; - checkit.GetFirst(&collision,filespec); - - wxLogWarning(wxT("Audacity found an orphan block file: %s. \nPlease consider saving and reloading the project to perform a complete project check."), - collision); - - return FALSE; - } - } - fileName.Assign(dir.GetFullPath(),value); - return fileName.IsOk(); -} - -static inline unsigned int hexchar_to_int(unsigned int x) -{ - if(x<48U)return 0; - if(x<58U)return x-48U; - if(x<65U)return 10U; - if(x<71U)return x-55U; - if(x<97U)return 10U; - if(x<103U)return x-87U; - return 15U; -} - -int DirManager::BalanceMidAdd(int topnum, int midkey) -{ - // enter the midlevel directory if it doesn't exist - - auto &balanceInfo = GetBalanceInfo(); - auto &dirMidPool = balanceInfo.dirMidPool; - auto &dirMidFull = balanceInfo.dirMidFull; - auto &dirTopPool = balanceInfo.dirTopPool; - auto &dirTopFull = balanceInfo.dirTopFull; - - if(dirMidPool.find(midkey) == dirMidPool.end() && - dirMidFull.find(midkey) == dirMidFull.end()){ - dirMidPool[midkey]=0; - - // increment toplevel directory fill - dirTopPool[topnum]++; - if(dirTopPool[topnum]>=256){ - // this toplevel is now full; move it to the full hash - dirTopPool.erase(topnum); - dirTopFull[topnum]=256; - } - return 1; - } - return 0; -} - -void DirManager::BalanceFileAdd(int midkey) -{ - auto &balanceInfo = GetBalanceInfo(); - auto &dirMidPool = balanceInfo.dirMidPool; - auto &dirMidFull = balanceInfo.dirMidFull; - - // increment the midlevel directory usage information - if(dirMidPool.find(midkey) != dirMidPool.end()){ - dirMidPool[midkey]++; - if(dirMidPool[midkey]>=256){ - // this middir is now full; move it to the full hash - dirMidPool.erase(midkey); - dirMidFull[midkey]=256; - } - }else{ - // this case only triggers in absurdly large projects; we still - // need to track directory fill even if we're over 256/256/256 - dirMidPool[midkey]++; - } -} - -void DirManager::BalanceInfoAdd(const wxString &file) -{ - const wxChar *s=file; - if(s[0]==wxT('e')){ - // this is one of the modern two-deep managed files - // convert filename to keys - unsigned int topnum = (hexchar_to_int(s[1]) << 4) | - hexchar_to_int(s[2]); - unsigned int midnum = (hexchar_to_int(s[3]) << 4) | - hexchar_to_int(s[4]); - unsigned int midkey=topnum<<8|midnum; - - BalanceMidAdd(topnum,midkey); - BalanceFileAdd(midkey); - } -} - -auto DirManager::GetBalanceInfo() -> BalanceInfo & -{ - // Before returning the map, - // see whether any block files have disappeared, - // and if so update - - auto count = BlockFile::gBlockFileDestructionCount; - if ( mLastBlockFileDestructionCount != count ) { - auto it = mBlockFileHash.begin(), end = mBlockFileHash.end(); - while (it != end) - { - BlockFilePtr ptr { it->second.lock() }; - if (!ptr) { - auto name = it->first; - mBlockFileHash.erase( it++ ); - BalanceInfoDel( name ); - } - else - ++it; - } - } - - mLastBlockFileDestructionCount = count; - - return mBalanceInfo; -} - -// Note that this will try to clean up directories out from under even -// locked blockfiles; this is actually harmless as the rmdir will fail -// on non-empty directories. -void DirManager::BalanceInfoDel(const wxString &file) -{ - // do not use GetBalanceInfo(), - // rather this function will be called from there. - auto &balanceInfo = mBalanceInfo; - auto &dirMidPool = balanceInfo.dirMidPool; - auto &dirMidFull = balanceInfo.dirMidFull; - auto &dirTopPool = balanceInfo.dirTopPool; - auto &dirTopFull = balanceInfo.dirTopFull; - - const wxChar *s=file; - if(s[0]==wxT('e')){ - // this is one of the modern two-deep managed files - - unsigned int topnum = (hexchar_to_int(s[1]) << 4) | - hexchar_to_int(s[2]); - unsigned int midnum = (hexchar_to_int(s[3]) << 4) | - hexchar_to_int(s[4]); - unsigned int midkey=topnum<<8|midnum; - - // look for midkey in the mid pool - if(dirMidFull.find(midkey) != dirMidFull.end()){ - // in the full pool - - if(--dirMidFull[midkey]<256){ - // move out of full into available - dirMidPool[midkey]=dirMidFull[midkey]; - dirMidFull.erase(midkey); - } - }else{ - if(--dirMidPool[midkey]<1){ - // erasing the key here is OK; we have provision to add it - // back if its needed (unlike the dirTopPool hash) - dirMidPool.erase(midkey); - - // DELETE the actual directory - auto dir = !projFull.empty() ? projFull : mytemp; - dir += wxFILE_SEP_PATH; - dir += file.Mid(0,3); - dir += wxFILE_SEP_PATH; - dir += wxT("d"); - dir += file.Mid(3,2); - wxFileName::Rmdir(dir); - - // also need to remove from toplevel - if(dirTopFull.find(topnum) != dirTopFull.end()){ - // in the full pool - if(--dirTopFull[topnum]<256){ - // move out of full into available - dirTopPool[topnum]=dirTopFull[topnum]; - dirTopFull.erase(topnum); - } - }else{ - if(--dirTopPool[topnum]<1){ - // do *not* erase the hash entry from dirTopPool - // *do* DELETE the actual directory - dir=(!projFull.empty()? projFull: mytemp); - dir += wxFILE_SEP_PATH; - dir += file.Mid(0,3); - wxFileName::Rmdir(dir); - } - } - } - } - } -} - -// only determines appropriate filename and subdir balance; does not -// perform maintainence -wxFileNameWrapper DirManager::MakeBlockFileName() -{ - auto &balanceInfo = GetBalanceInfo(); - auto &dirMidPool = balanceInfo.dirMidPool; - auto &dirTopPool = balanceInfo.dirTopPool; - auto &dirTopFull = balanceInfo.dirTopFull; - - wxFileNameWrapper ret; - wxString baseFileName; - - unsigned int filenum,midnum,topnum,midkey; - - while(1){ - - /* blockfiles are divided up into hierarchical directories. - Each toplevel directory is represented by "e" + two unique - hexadecimal digits, for a total possible number of 256 - toplevels. Each toplevel contains up to 256 subdirs named - "d" + two hex digits. Each subdir contains 'a number' of - files. */ - - filenum=0; - midnum=0; - topnum=0; - - // first action: if there is no available two-level directory in - // the available pool, try to make one - - if(dirMidPool.empty()){ - - // is there a toplevel directory with space for a NEW subdir? - - if(!dirTopPool.empty()){ - - // there's still a toplevel with room for a subdir - - DirHash::iterator iter = dirTopPool.begin(); - int newcount = 0; - topnum = iter->first; - - - // search for unused midlevels; linear search adequate - // add 32 NEW topnum/midnum dirs full of prospective filenames to midpool - for(midnum=0;midnum<256;midnum++){ - midkey=(topnum<<8)+midnum; - if(BalanceMidAdd(topnum,midkey)){ - newcount++; - if(newcount>=32)break; - } - } - - if(dirMidPool.empty()){ - // all the midlevels in this toplevel are in use yet the - // toplevel claims some are free; this implies multiple - // internal logic faults, but simply giving up and going - // into an infinite loop isn't acceptible. Just in case, - // for some reason, we get here, dynamite this toplevel so - // we don't just fail. - - // this is 'wrong', but the best we can do given that - // something else is also wrong. It will contain the - // problem so we can keep going without worry. - dirTopPool.erase(topnum); - dirTopFull[topnum]=256; - } - continue; - } - } - - if(dirMidPool.empty()){ - // still empty, thus an absurdly large project; all dirs are - // full to 256/256/256; keep working, but fall back to 'big - // filenames' and randomized placement - - filenum = rand(); - midnum = (int)(256.*rand()/(RAND_MAX+1.)); - topnum = (int)(256.*rand()/(RAND_MAX+1.)); - midkey=(topnum<<8)+midnum; - - - }else{ - - DirHash::iterator iter = dirMidPool.begin(); - midkey = iter->first; - - // split the retrieved 16 bit directory key into two 8 bit numbers - topnum = midkey >> 8; - midnum = midkey & 0xff; - filenum = (int)(4096.*rand()/(RAND_MAX+1.)); - - } - - baseFileName.Printf(wxT("e%02x%02x%03x"),topnum,midnum,filenum); - - if (!ContainsBlockFile(baseFileName)) { - // not in the hash, good. - if (!this->AssignFile(ret, baseFileName, true)) - { - // this indicates an on-disk collision, likely due to an - // orphan blockfile. We should try again, but first - // alert the balancing info there's a phantom file here; - // if the directory is nearly full of orphans we neither - // want performance to suffer nor potentially get into an - // infinite loop if all possible filenames are taken by - // orphans (unlikely but possible) - BalanceFileAdd(midkey); - - }else break; - } - } - // FIXME: Might we get here without midkey having been set? - // Seemed like a possible problem in these changes in .aup directory hierarchy. - BalanceFileAdd(midkey); - return ret; -} - -BlockFilePtr DirManager::NewBlockFile( const BlockFileFactory &factory ) -{ - wxFileNameWrapper filePath{ MakeBlockFileName() }; - const wxString fileName{ filePath.GetName() }; - auto newBlockFile = factory( std::move(filePath) ); - mBlockFileHash[fileName] = newBlockFile; - auto &aliasName = newBlockFile->GetExternalFileName(); - if ( aliasName.IsOk() ) - //I don't immediately see a place where aliased files remove when a file is closed. - aliasList.push_back( aliasName.GetFullPath() ); - return newBlockFile; -} - -bool DirManager::ContainsBlockFile(const BlockFile *b) const -{ - if (!b) - return false; - auto result = b->GetFileName(); - BlockHash::const_iterator it = mBlockFileHash.find(result.name.GetName()); - if (it == mBlockFileHash.end()) - return false; - BlockFilePtr ptr = it->second.lock(); - return ptr && (b == &*ptr); -} - -bool DirManager::ContainsBlockFile(const wxString &filepath) const -{ - // check what the hash returns in case the blockfile is from a different project - BlockHash::const_iterator it = mBlockFileHash.find(filepath); - return it != mBlockFileHash.end() && - BlockFilePtr{ it->second.lock() }; -} - -// Adds one to the reference count of the block file, -// UNLESS it is "locked", then it makes a NEW copy of -// the BlockFile. -// This function returns non-NULL, or else throws -BlockFilePtr DirManager::CopyBlockFile(const BlockFilePtr &b) -{ - if (!b) - THROW_INCONSISTENCY_EXCEPTION; - - auto result = b->GetFileName(); - const auto &fn = result.name; - - if (!b->IsLocked()) { - //mchinen:July 13 2009 - not sure about this, but it needs to be added to the hash to be able to save if not locked. - //note that this shouldn't hurt mBlockFileHash's that already contain the filename, since it should just overwrite. - //but it's something to watch out for. - // - // LLL: Except for silent block files which have uninitialized filename. - if (fn.IsOk()) - mBlockFileHash[fn.GetName()] = b; - return b; - } - - // Copy the blockfile - BlockFilePtr b2; - if (!fn.IsOk()) - // Block files with uninitialized filename (i.e. SilentBlockFile) - // just need an in-memory copy. - b2 = b->Copy(wxFileNameWrapper{}); - else - { - wxFileNameWrapper newFile{ MakeBlockFileName() }; - const wxString newName{newFile.GetName()}; - const wxString newPath{ newFile.GetFullPath() }; - - // We assume that the NEW file should have the same extension - // as the existing file - newFile.SetExt(fn.GetExt()); - - if( !FileNames::DoCopyFile(fn.GetFullPath(), - newFile.GetFullPath()) ) - // Disk space exhaustion, maybe - throw FileException{ - FileException::Cause::Write, newFile }; - - b2 = b->Copy(std::move(newFile)); - - mBlockFileHash[newName] = b2; - aliasList.push_back(newPath); - } - - if (!b2) - THROW_INCONSISTENCY_EXCEPTION; - - return b2; -} - -namespace { - -using Deserializers = - std::unordered_map< wxString, DirManager::BlockFileDeserializer >; -Deserializers &GetDeserializers() -{ - static Deserializers sDeserializers; - return sDeserializers; -} - -} - -DirManager::RegisteredBlockFileDeserializer::RegisteredBlockFileDeserializer( - const wxString &tag, BlockFileDeserializer function ) -{ - GetDeserializers()[tag] = function; -} - -bool DirManager::HandleXMLTag(const wxChar *tag, const wxChar **attrs) -{ - if( !mLoadingTarget ) - return false; - - BlockFilePtr pBlockFile {}; - - BlockFilePtr &target = mLoadingTarget(); - mLoadingTarget = nullptr; - - auto &table = GetDeserializers(); - auto iter = table.find( tag ); - if ( iter == table.end() ) - return false; - pBlockFile = iter->second( *this, attrs ); - - if (!pBlockFile) - // BuildFromXML failed, or we didn't find a valid blockfile tag. - return false; - - if (!pBlockFile->GetFileName().name.IsOk()) { - // Silent blocks don't actually have a file associated, so - // we don't need to worry about the hash table at all - target = pBlockFile; - return true; - } - - // Check the length here so we don't have to do it in each BuildFromXML method. - if ((mMaxSamples != ~size_t(0)) && // is initialized - (pBlockFile->GetLength() > mMaxSamples)) - { - // See http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c13. - // Lock pBlockFile so that the ~BlockFile() will not DELETE the file on disk. - pBlockFile->Lock(); - return false; - } - else - target = pBlockFile; - - // - // If the block we loaded is already in the hash table, then the - // object we just loaded is a duplicate, so we DELETE it and - // return a reference to the existing object instead. - // - - wxString name = target->GetFileName().name.GetName(); - auto &wRetrieved = mBlockFileHash[name]; - BlockFilePtr retrieved = wRetrieved.lock(); - if (retrieved) { - // Lock it in order to DELETE it safely, i.e. without having - // it DELETE the file, too... - target->Lock(); - - target = retrieved; - return true; - } - - // This is a NEW object - wRetrieved = target; - // MakeBlockFileName wasn't used so we must add the directory - // balancing information - BalanceInfoAdd(name); - - return true; -} - -std::pair DirManager::LinkOrCopyToNewProjectDirectory( - BlockFile *f, bool &link ) -{ - FilePath newPath; - auto result = f->GetFileName(); - const auto &oldFileNameRef = result.name; - - // Check that this BlockFile corresponds to a file on disk - //ANSWER-ME: Is this checking only for SilentBlockFiles, in which case - // (!oldFileName.IsOk()) is a more correct check? - if (oldFileNameRef.GetName().empty()) { - return { true, newPath }; - } - - wxFileNameWrapper newFileName; - if (!this->AssignFile(newFileName, oldFileNameRef.GetFullName(), false) - // Another sanity check against blockfiles getting reassigned an empty - // name, as apparently happened in - // http://forum.audacityteam.org/viewtopic.php?f=47&t=97787 : - || newFileName.GetFullName().empty() ) - return { false, {} }; - - newPath = newFileName.GetFullPath(); - - if (newFileName != oldFileNameRef) { - auto oldPath = oldFileNameRef.GetFullPath(); - bool success = false; - if (link) - success = FileNames::HardLinkFile( oldPath, newPath ); - if (!success) - link = false, - success = FileNames::DoCopyFile( oldPath, newPath ); - if (!success) - return { false, {} }; - } - - return { true, newPath }; -} - -bool DirManager::EnsureSafeFilename(const wxFileName &fName) -{ - // Quick check: If it's not even in our alias list, - // then the file name is A-OK. - - const wxString fullPath{fName.GetFullPath()}; - if ( !make_iterator_range( aliasList ).contains( fullPath ) ) - return true; - - // Figure out what the NEW name for the existing file would be. - // Keep trying until we find a filename that doesn't exist. - - wxFileNameWrapper renamedFileName{ fName }; - int i = 0; - do { - i++; - /* i18n-hint: This is the pattern for filenames that are created - * when a file needs to be backed up to a different name. For - * example, mysong would become mysong-old1, mysong-old2, etc. - * 'old' is part of a filename used when a file is renamed. - * e.g. Try to go from "mysong.wav" to "mysong-old1.wav". */ - renamedFileName.SetName(wxString::Format(_("%s-old%d"), fName.GetName(), i)); - } while (renamedFileName.FileExists()); - - // Test creating a file by that name to make sure it will - // be possible to do the rename - - const wxString renamedFullPath{ renamedFileName.GetFullPath() }; - wxFile testFile(renamedFullPath, wxFile::write); - if (!testFile.IsOpened()) { - { // need braces to avoid compiler warning about ambiguous else, see the macro - wxLogSysError(wxT("Unable to open/create test file."), - renamedFullPath); - } - return false; - } - - // Close the file prior to renaming. - testFile.Close(); - - if (!wxRemoveFile(renamedFullPath)) { - { // need braces to avoid compiler warning about ambiguous else, see the macro - /* i18n-hint: %s is the name of a file.*/ - wxLogSysError(wxT("Unable to remove '%s'."), - renamedFullPath); - } - return false; - } - - wxPrintf(_("Renamed file: %s\n"), renamedFullPath); - - // Go through our block files and see if any indeed point to - // the file we're concerned about. If so, point the block file - // to the renamed file and when we're done, perform the rename. - - bool needToRename = false; - wxBusyCursor busy; - BlockHash::iterator iter = mBlockFileHash.begin(); - while (iter != mBlockFileHash.end()) - { - BlockFilePtr b = iter->second.lock(); - if (b) { - if (fName.IsOk() && b->GetExternalFileName() == fName) { - needToRename = true; - } - } - ++iter; - } - - if (needToRename) { - if (!wxRenameFile(fullPath, - renamedFullPath)) - { - // ACK!!! The renaming was unsuccessful!!! - // (This shouldn't happen, since we tried creating a - // file of this name and then deleted it just a - // second earlier.) But we'll handle this scenario - // just in case!!! - - // Print error message and cancel the export - wxLogSysError(wxT("Unable to rename '%s' to '%s'."), - fullPath, - renamedFullPath); - - return false; - } - else - { - //point the aliases to the NEW filename. - BlockHash::iterator iter2 = mBlockFileHash.begin(); - while (iter2 != mBlockFileHash.end()) - { - BlockFilePtr b = iter2->second.lock(); - if (b) { - if (fName.IsOk() && b->GetExternalFileName() == fName) { - b->SetExternalFileName(wxFileNameWrapper{ renamedFileName }); - wxPrintf(_("Changed block %s to new alias name\n"), - b->GetFileName().name.GetFullName()); - - } - } - ++iter2; - } - - } - - aliasList.erase( - std::find( aliasList.begin(), aliasList.end(), fullPath ) ); - aliasList.push_back(renamedFullPath); - } - - // Success!!! Either we successfully renamed the file, - // or we didn't need to! - return true; -} - -void DirManager::FindMissingAliasFiles( - BlockHash& missingAliasFilesAUFHash, // output: (.auf) AliasBlockFiles whose aliased files are missing - BlockHash& missingAliasFilesPathHash) // output: full paths of missing aliased files -{ - BlockHash::iterator iter = mBlockFileHash.begin(); - while (iter != mBlockFileHash.end()) - { - wxString key = iter->first; // file name and extension - BlockFilePtr b = iter->second.lock(); - if (b) { - if (b->IsAlias()) - { - const wxFileName &aliasedFileName = - static_cast< AliasBlockFile* > ( &*b )->GetAliasedFileName(); - wxString aliasedFileFullPath = aliasedFileName.GetFullPath(); - // wxEmptyString can happen if user already chose to "replace... with silence". - if ((!aliasedFileFullPath.empty()) && - !aliasedFileName.FileExists()) - { - missingAliasFilesAUFHash[key] = b; - if (missingAliasFilesPathHash.find(aliasedFileFullPath) == - missingAliasFilesPathHash.end()) // Add it only once. - // Not actually using the block here, just the path, - // so set the block to NULL to create the entry. - missingAliasFilesPathHash[aliasedFileFullPath] = {}; - } - } - } - ++iter; - } - - iter = missingAliasFilesPathHash.begin(); - while (iter != missingAliasFilesPathHash.end()) - { - wxLogWarning(wxT("Missing aliased audio file: '%s'"), iter->first); - ++iter; - } -} - -void DirManager::FindMissingAUFs( - BlockHash& missingAUFHash) // output: missing (.auf) AliasBlockFiles -{ - BlockHash::iterator iter = mBlockFileHash.begin(); - while (iter != mBlockFileHash.end()) - { - const wxString &key = iter->first; - BlockFilePtr b = iter->second.lock(); - if (b) { - if (b->IsAlias()) - { - /* don't look in hash; that might find files the user moved - that the Blockfile abstraction can't find itself */ - wxFileNameWrapper fileName{ MakeBlockFilePath(key) }; - fileName.SetName(key); - fileName.SetExt(wxT("auf")); - if (!fileName.FileExists()) - { - missingAUFHash[key] = b; - wxLogWarning(wxT("Missing alias (.auf) block file: '%s'"), - fileName.GetFullPath()); - } - } - } - ++iter; - } -} - -void DirManager::FindMissingAUs( - BlockHash& missingAUHash) // missing data (.au) blockfiles -{ - BlockHash::iterator iter = mBlockFileHash.begin(); - while (iter != mBlockFileHash.end()) - { - const wxString &key = iter->first; - BlockFilePtr b = iter->second.lock(); - // TODO key can be empty in doing a ProjectFSK - // In which case MakeFilePath will fail. Bail out? - if (b) { - if (!b->IsAlias()) - { - wxFileNameWrapper fileName{ MakeBlockFilePath(key) }; - fileName.SetName(key); - fileName.SetExt(wxT("au")); - const auto path = fileName.GetFullPath(); - if (!fileName.FileExists() || - wxFile{ path }.Length() == 0) - { - missingAUHash[key] = b; - wxLogWarning(wxT("Missing data block file: '%s'"), path); - } - } - } - ++iter; - } -} - -// Find .au and .auf files that are not in the project. -void DirManager::FindOrphanBlockFiles( - const FilePaths &filePathArray, // input: all files in project directory - FilePaths &orphanFilePathArray) // output: orphan files -{ - std::vector< std::shared_ptr > otherDirManagers; - for ( auto &wPtr : sDirManagers ) { - auto sPtr = wPtr.lock(); - if ( sPtr && sPtr.get() != this ) - otherDirManagers.push_back( sPtr ); - } - - for (size_t i = 0; i < filePathArray.size(); i++) - { - const wxFileName &fullname = filePathArray[i]; - wxString basename = fullname.GetName(); - const wxString ext{fullname.GetExt()}; - if ((mBlockFileHash.find(basename) == mBlockFileHash.end()) && // is orphan - // Consider only Audacity data files. - // Specifically, ignore JPG and OGG ("Save Compressed Copy"). - (ext.IsSameAs(wxT("au"), false) || - ext.IsSameAs(wxT("auf"), false))) - { - // Ignore it if it exists in the clipboard (from a previously closed project) - if ( std::any_of( otherDirManagers.begin(), otherDirManagers.end(), - [&]( const std::shared_ptr< DirManager > &ptr ){ - return ptr->ContainsBlockFile( basename ); - } - ) ) - continue; - - orphanFilePathArray.push_back(fullname.GetFullPath()); - } - } - for ( const auto &orphan : orphanFilePathArray ) - wxLogWarning(wxT("Orphan block file: '%s'"), orphan); -} - - -void DirManager::RemoveOrphanBlockfiles() -{ - FilePaths filePathArray; // *all* files in the project directory/subdirectories - auto dirPath = (!projFull.empty() ? projFull : mytemp); - RecursivelyEnumerateWithProgress( - dirPath, - filePathArray, // output: all files in project directory tree - wxEmptyString, // All dirs - wxEmptyString, // All files - true, false, - mBlockFileHash.size(), // rough guess of how many BlockFiles will be found/processed, for progress - XO("Inspecting project file data")); - - FilePaths orphanFilePathArray; - this->FindOrphanBlockFiles( - filePathArray, // input: all files in project directory tree - orphanFilePathArray); // output: orphan files - - // Remove all orphan blockfiles. - for ( const auto &orphan : orphanFilePathArray ) - wxRemoveFile(orphan); -} - diff --git a/src/DirManager.h b/src/DirManager.h deleted file mode 100644 index 9034dc536..000000000 --- a/src/DirManager.h +++ /dev/null @@ -1,274 +0,0 @@ -/********************************************************************** - - Audacity: A Digital Audio Editor - - DirManager.h - - Dominic Mazzoni - -**********************************************************************/ - -#ifndef _DIRMANAGER_ -#define _DIRMANAGER_ - -#include "audacity/Types.h" -#include "xml/XMLTagHandler.h" - -#include -#include -#include - -#include "ClientData.h" - -class wxFileNameWrapper; -class AudacityProject; -class BlockArray; -class BlockFile; -class ProgressDialog; - -using DirHash = std::unordered_map; - -class BlockFile; -using BlockFilePtr = std::shared_ptr; - -using BlockHash = std::unordered_map< wxString, std::weak_ptr >; - -wxMemorySize GetFreeMemory(); - -enum { - kCleanTopDirToo = 1, // The top directory can be excluded from clean. - kCleanDirsOnlyIfEmpty = 2, // Otherwise non empty dirs may be removed. - kCleanFiles = 4, // Remove files - kCleanDirs = 8 // Remove dirs. -}; - - -class PROFILE_DLL_API DirManager final - : public XMLTagHandler - , public ClientData::Base - , public std::enable_shared_from_this< DirManager > -{ - public: - - static DirManager &Get( AudacityProject &project ); - static const DirManager &Get( const AudacityProject &project ); - static DirManager &Reset( AudacityProject &project ); - static void Destroy( AudacityProject &project ); - - static int RecursivelyEnumerate(const FilePath &dirPath, - FilePaths& filePathArray, // output: all files in dirPath tree - wxString dirspec, - wxString filespec, - bool bFiles, bool bDirs, - int progress_count = 0, - int progress_bias = 0, - ProgressDialog* progress = nullptr); - - static int RecursivelyEnumerateWithProgress(const FilePath &dirPath, - FilePaths& filePathArray, // output: all files in dirPath tree - wxString dirspec, - wxString filespec, - bool bFiles, bool bDirs, - int progress_count, - const TranslatableString &message); - - static int RecursivelyCountSubdirs( const FilePath &dirPath ); - - static int RecursivelyRemoveEmptyDirs(const FilePath &dirPath, - int nDirCount = 0, - ProgressDialog* pProgress = nullptr); - - static void RecursivelyRemove(const FilePaths& filePathArray, int count, int bias, - int flags, const TranslatableString &message = {}); - - // Type of a function that builds a block file, using attributes from XML - using BlockFileDeserializer = - std::function< BlockFilePtr( DirManager&, const wxChar ** ) >; - // Typically a statically declared object, - // registers a function for an XML tag - struct RegisteredBlockFileDeserializer { - RegisteredBlockFileDeserializer( - const wxString &tag, BlockFileDeserializer function ); - }; - - private: - // MM: Construct DirManager - // Don't call this directly but use Create() instead - DirManager(); - - public: - - static std::shared_ptr< DirManager > Create(); - - DirManager( const DirManager & ) PROHIBITED; - DirManager &operator=( const DirManager & ) PROHIBITED; - virtual ~DirManager(); - - size_t NumBlockFiles() const { return mBlockFileHash.size(); } - - static void SetTempDir(const wxString &_temp) { globaltemp = _temp; } - - class ProjectSetter - { - public: - ProjectSetter( - DirManager &dirManager, - FilePath& newProjPath, // assigns it if empty - const FilePath& newProjName, const bool bCreate, bool moving); - ~ProjectSetter(); - - bool Ok(); - void Commit(); - - private: - struct Impl; - std::unique_ptr mpImpl; - }; - - // Returns true on success. - // If SetProject is told NOT to create the directory - // but it doesn't already exist, SetProject fails and returns false. - // This function simply creates a ProjectSetter and commits it if successful. - // Using ProjectSetter directly allows separation of those steps. - bool SetProject( - FilePath& newProjPath, // assigns it if empty - const FilePath& newProjName, const bool bCreate); - - FilePath GetProjectDataDir(); - FilePath GetProjectName(); - - wxLongLong GetFreeDiskSpace(); - - using BlockFileFactory = std::function< BlockFilePtr( wxFileNameWrapper ) >; - BlockFilePtr NewBlockFile( const BlockFileFactory &factory ); - - /// Returns true if the blockfile pointed to by b is contained by the DirManager - bool ContainsBlockFile(const BlockFile *b) const; - /// Check for existing using filename using complete filename - bool ContainsBlockFile(const wxString &filepath) const; - - // Adds one to the reference count of the block file, - // UNLESS it is "locked", then it makes a NEW copy of - // the BlockFile. - // May throw an exception in case of disk space exhaustion, otherwise - // returns non-null. - BlockFilePtr CopyBlockFile(const BlockFilePtr &b); - - BlockFile *LoadBlockFile(const wxChar **attrs, sampleFormat format); - void SaveBlockFile(BlockFile *f, int depth, FILE *fp); - -#if LEGACY_PROJECT_FILE_SUPPORT - BlockFile *LoadBlockFile(wxTextFile * in, sampleFormat format); - void SaveBlockFile(BlockFile * f, wxTextFile * out); -#endif - - std::pair - LinkOrCopyToNewProjectDirectory(BlockFile *f, bool &link); - - bool EnsureSafeFilename(const wxFileName &fName); - - using LoadingTarget = std::function< BlockFilePtr &() >; - void SetLoadingTarget( LoadingTarget loadingTarget ) - { - mLoadingTarget = loadingTarget; - } - sampleFormat GetLoadingFormat() const { return mLoadingFormat; } - void SetLoadingFormat(sampleFormat format) { mLoadingFormat = format; } - size_t GetLoadingBlockLength() const { return mLoadingBlockLen; } - void SetLoadingBlockLength(size_t len) { mLoadingBlockLen = len; } - - // Note: following affects only the loading of block files when opening a project - void SetLoadingMaxSamples(size_t max) { mMaxSamples = max; } - - bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override; - XMLTagHandler *HandleXMLChild(const wxChar * WXUNUSED(tag)) override - { return NULL; } - bool AssignFile(wxFileNameWrapper &filename, const wxString &value, bool check); - - // Clean the temp dir. Note that now where we have auto recovery the temp - // dir is not cleaned at start up anymore. But it is cleaned when the - // program is exited normally. - static void CleanTempDir(); - static void CleanDir( - const FilePath &path, - const wxString &dirSpec, - const wxString &fileSpec, - const TranslatableString &msg, - int flags = 0); - - void FindMissingAliasFiles( - BlockHash& missingAliasFilesAUFHash, // output: (.auf) AliasBlockFiles whose aliased files are missing - BlockHash& missingAliasFilesPathHash); // output: full paths of missing aliased files - void FindMissingAUFs( - BlockHash& missingAUFHash); // output: missing (.auf) AliasBlockFiles - void FindMissingAUs( - BlockHash& missingAUHash); // missing data (.au) blockfiles - // Find .au and .auf files that are not in the project. - void FindOrphanBlockFiles( - const FilePaths &filePathArray, // input: all files in project directory - FilePaths &orphanFilePathArray); // output: orphan files - - - // Remove all orphaned blockfiles without user interaction. This is - // generally safe, because orphaned blockfiles are not referenced by the - // project and thus worthless anyway. - void RemoveOrphanBlockfiles(); - - // Get directory where data files are in. Note that projects are normally - // not interested in this information, but it is important for the - // auto-save functionality - FilePath GetDataFilesDir() const; - - // This should only be used by the auto save functionality - void SetLocalTempDir(const wxString &path); - - // Do not DELETE any temporary files on exit. This is only called if - // auto recovery is cancelled and should be retried later - static void SetDontDeleteTempFiles() { dontDeleteTempFiles = true; } - - private: - - wxFileNameWrapper MakeBlockFileName(); - wxFileNameWrapper MakeBlockFilePath(const wxString &value); - - BlockHash mBlockFileHash; // repository for blockfiles - - // Hashes for management of the sub-directory tree of _data - struct BalanceInfo - { - DirHash dirTopPool; // available toplevel dirs - DirHash dirTopFull; // full toplevel dirs - DirHash dirMidPool; // available two-level dirs - DirHash dirMidFull; // full two-level dirs - } mBalanceInfo; - - // Accessor for the balance info, may need to do a delayed update for - // deletion in case other threads DELETE block files - BalanceInfo &GetBalanceInfo(); - - void BalanceInfoDel(const wxString&); - void BalanceInfoAdd(const wxString&); - void BalanceFileAdd(int); - int BalanceMidAdd(int, int); - - FilePath projName; - FilePath projPath; - FilePath projFull; - - FilePaths aliasList; - - LoadingTarget mLoadingTarget; - sampleFormat mLoadingFormat; - size_t mLoadingBlockLen; - - size_t mMaxSamples; // max samples per block - - unsigned long mLastBlockFileDestructionCount { 0 }; - - static wxString globaltemp; - wxString mytemp; - static int numDirManagers; - static bool dontDeleteTempFiles; -}; - -#endif diff --git a/src/Envelope.h b/src/Envelope.h index ee6423876..d354a32d6 100644 --- a/src/Envelope.h +++ b/src/Envelope.h @@ -21,7 +21,6 @@ class wxRect; class wxMouseEvent; class wxTextFile; -class DirManager; class Envelope; class EnvPoint; @@ -110,12 +109,6 @@ public: double ClampValue(double value) { return std::max(mMinValue, std::min(mMaxValue, value)); } -#if LEGACY_PROJECT_FILE_SUPPORT - // File I/O - - bool Load(wxTextFile * in, DirManager * dirManager) override; - bool Save(wxTextFile * out, bool overwrite) override; -#endif // Newfangled XML file I/O bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override; XMLTagHandler *HandleXMLChild(const wxChar *tag) override; diff --git a/src/FileFormats.h b/src/FileFormats.h index 46e8145bd..0bdac41ac 100644 --- a/src/FileFormats.h +++ b/src/FileFormats.h @@ -15,6 +15,8 @@ #include "audacity/Types.h" +#include "MemoryX.h" + #include "sndfile.h" class ChoiceSetting; diff --git a/src/FileNames.cpp b/src/FileNames.cpp index e8d5ee745..8423f023e 100644 --- a/src/FileNames.cpp +++ b/src/FileNames.cpp @@ -53,7 +53,7 @@ const FileNames::FileType FileNames::AllFiles{ XO("All files"), { wxT("") } } /* i18n-hint an Audacity project is the state of the program, stored as files that can be reopened to resume the session later */ - , FileNames::AudacityProjects{ XO("Audacity projects"), { wxT("aup") }, true } + , FileNames::AudacityProjects{ XO("Audacity projects"), { wxT("aup3") }, true } , FileNames::DynamicLibraries{ #if defined(__WXMSW__) XO("Dynamically Linked Libraries"), { wxT("dll") }, true @@ -220,16 +220,6 @@ void FileNames::MakeNameUnique(FilePaths &otherNames, otherNames.push_back(newName.GetFullName()); } - - -// -// Audacity user data directories -FilePath FileNames::AutoSaveDir() -{ - wxFileName autoSaveDir(FileNames::DataDir(), wxT("AutoSave")); - return FileNames::MkDir(autoSaveDir.GetFullPath()); -} - // The APP name has upercase first letter (so that Quit Audacity is correctly // capitalised on Mac, but we want lower case APP name in paths. // This function does that substitution, IF the last component of @@ -739,10 +729,36 @@ char *FileNames::VerifyFilename(const wxString &s, bool input) } #endif -//using this with wxStringArray::Sort will give you a list that -//is alphabetical, without depending on case. If you use the -//default sort, you will get strings with 'R' before 'a', because it is in caps. +// Using this with wxStringArray::Sort will give you a list that +// is alphabetical, without depending on case. If you use the +// default sort, you will get strings with 'R' before 'a', because it is in caps. int FileNames::CompareNoCase(const wxString& first, const wxString& second) { return first.CmpNoCase(second); } + +// Create a unique filename using the passed prefix and suffix +wxString FileNames::CreateUniqueName(const wxString &prefix, + const wxString &suffix /* = wxEmptyString */) +{ + static int count = 0; + + return wxString::Format(wxT("%s %s N-%i.%s"), + prefix, + wxDateTime::Now().Format(wxT("%Y-%m-%d %H-%M-%S")), + ++count, + suffix); +} + +wxString FileNames::UnsavedProjectExtension() +{ + return wxT("aup3unsaved"); +} + +wxString FileNames::UnsavedProjectFileName() +{ + wxFileName fn(TempDir(), + CreateUniqueName(wxT("New Project"), UnsavedProjectExtension())); + + return fn.GetFullPath(); +} diff --git a/src/FileNames.h b/src/FileNames.h index 9c2310ce8..06d9d6d6c 100644 --- a/src/FileNames.h +++ b/src/FileNames.h @@ -43,7 +43,7 @@ namespace FileNames // Frequently used types extern const FileType AllFiles // * - , AudacityProjects // *.aup + , AudacityProjects // *.aup3 , DynamicLibraries // depends on the operating system , TextFiles // *.txt , XMLFiles; // *.xml, *.XML @@ -95,7 +95,6 @@ namespace FileNames * windows system */ FilePath DataDir(); FilePath ResourcesDir(); - FilePath AutoSaveDir(); FilePath HtmlHelpDir(); FilePath HtmlHelpIndexFile(bool quick); FilePath LegacyChainDir(); @@ -182,6 +181,17 @@ namespace FileNames // wxString compare function for sorting case, which is needed to load correctly. int CompareNoCase(const wxString& first, const wxString& second); + + // Create a unique filename using the passed prefix and suffix + wxString CreateUniqueName(const wxString &prefix, + const wxString &suffix = wxEmptyString); + + // Create a filename for an unsaved/temporary project file + wxString UnsavedProjectFileName(); + + // File extension used for unsaved/temporary project files + wxString UnsavedProjectExtension(); + }; // Use this macro to wrap all filenames and pathnames that get diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index d3d6842bd..4db0675c1 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -68,11 +68,11 @@ static ProjectFileIORegistry::Entry registerFactory{ LabelTrack::Holder TrackFactory::NewLabelTrack() { - return std::make_shared(mDirManager); + return std::make_shared(&mProject); } -LabelTrack::LabelTrack(const std::shared_ptr &projDirManager): - Track(projDirManager), +LabelTrack::LabelTrack(AudacityProject *project): + Track(project), mClipLen(0.0), miLastLabel(-1) { @@ -627,53 +627,6 @@ void LabelTrack::WriteXML(XMLWriter &xmlFile) const xmlFile.EndTag(wxT("labeltrack")); } -#if LEGACY_PROJECT_FILE_SUPPORT -bool LabelTrack::Load(wxTextFile * in, DirManager * dirManager) -{ - if (in->GetNextLine() != wxT("NumMLabels")) - return false; - - unsigned long len; - if (!(in->GetNextLine().ToULong(&len))) - return false; - - mLabels.clear(); - mLabels.reserve(len); - - for (int i = 0; i < len; i++) { - double t0; - if (!Internat::CompatibleToDouble(in->GetNextLine(), &t0)) - return false; - // Legacy file format does not include label end-times. - // PRL: nothing NEW to do, legacy file support - mLabels.push_back(LabelStruct { - SelectedRegion{ t0, t0 }, in->GetNextLine() - }); - } - - if (in->GetNextLine() != wxT("MLabelsEnd")) - return false; - SortLabels(); - return true; -} - -bool LabelTrack::Save(wxTextFile * out, bool overwrite) -{ - out->AddLine(wxT("NumMLabels")); - int len = mLabels.size(); - out->AddLine(wxString::Format(wxT("%d"), len)); - - for (auto pLabel : mLabels) { - const auto &labelStruct = *pLabel; - out->AddLine(wxString::Format(wxT("%lf"), labelStruct.selectedRegion.mT0)); - out->AddLine(labelStruct.title); - } - out->AddLine(wxT("MLabelsEnd")); - - return true; -} -#endif - Track::Holder LabelTrack::Cut(double t0, double t1) { auto tmp = Copy(t0, t1); @@ -699,7 +652,7 @@ Track::Holder LabelTrack::SplitCut(double t0, double t1) Track::Holder LabelTrack::Copy(double t0, double t1, bool) const { - auto tmp = std::make_shared(GetDirManager()); + auto tmp = std::make_shared(GetProject()); const auto lt = static_cast(tmp.get()); for (auto &labelStruct: mLabels) { diff --git a/src/LabelTrack.h b/src/LabelTrack.h index aff64535a..48fd3792c 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -20,7 +20,6 @@ class wxTextFile; class AudacityProject; -class DirManager; class NotifyingSelectedRegion; class TimeWarper; @@ -88,7 +87,7 @@ class AUDACITY_DLL_API LabelTrack final , public wxEvtHandler { public: - LabelTrack(const std::shared_ptr &projDirManager); + LabelTrack(AudacityProject *project); LabelTrack(const LabelTrack &orig); virtual ~ LabelTrack(); @@ -113,11 +112,6 @@ public: XMLTagHandler *HandleXMLChild(const wxChar *tag) override; void WriteXML(XMLWriter &xmlFile) const override; -#if LEGACY_PROJECT_FILE_SUPPORT - bool Load(wxTextFile * in, DirManager * dirManager) override; - bool Save(wxTextFile * out, bool overwrite) override; -#endif - Track::Holder Cut (double t0, double t1) override; Track::Holder Copy (double t0, double t1, bool forClipboard = true) const override; void Clear(double t0, double t1) override; diff --git a/src/MissingAliasFileDialog.cpp b/src/MissingAliasFileDialog.cpp deleted file mode 100644 index 0d07d5af7..000000000 --- a/src/MissingAliasFileDialog.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/********************************************************************* - -\class MissingAliasFileDialog -\brief Special case of ErrorDialog for reporting missing alias files. - -*//*******************************************************************/ - -#include "MissingAliasFileDialog.h" - -#include -#include - -#include "BlockFile.h" -#include "DirManager.h" -#include "Project.h" -#include "widgets/ErrorDialog.h" - -namespace { - using wxDialogRef = wxWeakRef< wxDialog >; - std::vector< wxDialogRef > sDialogs; -} - -// special case for alias missing dialog because we keep track of if it exists. -class MissingAliasFileDialog final : public ErrorDialog -{ - public: - MissingAliasFileDialog(wxWindow *parent, - const TranslatableString & dlogTitle, - const TranslatableString & message, - const wxString & helpURL, - const bool Close = true, const bool modal = true); - virtual ~MissingAliasFileDialog(); -}; - - -MissingAliasFileDialog::MissingAliasFileDialog(wxWindow *parent, - const TranslatableString & dlogTitle, - const TranslatableString & message, - const wxString & helpURL, - const bool Close, const bool modal) -: ErrorDialog( parent, - dlogTitle, message, helpURL, Close, modal ) -{ - sDialogs.push_back( this ); -} - -MissingAliasFileDialog::~MissingAliasFileDialog() -{ - auto begin = sDialogs.begin(), end = sDialogs.end(), - newEnd = std::remove_if( begin, end, - [&]( const wxDialogRef &ref ){ - return ref == this; } ); - sDialogs.erase( newEnd, end ); -} - -namespace MissingAliasFilesDialog { - - namespace{ - bool m_missingAliasFilesWarningShouldShow{ true }; - std::weak_ptr< AudacityProject > m_LastMissingBlockFileProject; - wxString m_LastMissingBlockFilePath; - std::mutex m_LastMissingBlockFileLock; - } - - using Lock = std::unique_lock< std::mutex >; - - void Show(AudacityProject *project, - const TranslatableString &dlogTitle, - const TranslatableString &message, - const wxString &helpPage, - const bool Close) - { - auto parent = FindProjectFrame( project ); - wxASSERT(parent); // to justify safenew - ErrorDialog *dlog = safenew MissingAliasFileDialog(parent, dlogTitle, message, helpPage, Close, false); - // Don't center because in many cases (effect, export, etc) there will be a progress bar in the center that blocks this. - // instead put it just above or on the top of the project. - wxPoint point; - point.x = 0; - - point.y = parent ? parent->GetPosition().y - 200 : 100; - - if (point.y < 100) - point.y = 100; - dlog->SetPosition(point); - dlog->CentreOnParent(wxHORIZONTAL); - - // This needs to be modeless because user may need to - // stop playback AND read dialog's instructions. - dlog->Show(); - // ANSWER-ME: Vigilant Sentry flags this method as not deleting dlog, so a mem leak. - // PRL: answer is that the parent window guarantees destruction of the dialog - // but in practice Destroy() in OnOK does that - } - - wxDialog *Find( const AudacityProject &project ) - { - auto &window = GetProjectFrame( project ); - auto begin = sDialogs.begin(), end = sDialogs.end(), - iter = std::find_if( begin, end, - [&]( const wxDialogRef &ref ){ - return ref && ref->GetParent() == &window; } ); - if (iter != end) - return *iter; - return nullptr; - } - - void Mark(const AliasBlockFile *b) - { - Lock lock{ m_LastMissingBlockFileLock }; - if (b) { - for ( auto pProject : AllProjects{} ) { - // search each project for the blockfile - if (DirManager::Get( *pProject ).ContainsBlockFile(b)) { - m_LastMissingBlockFileProject = pProject; - break; - } - } - } - else - m_LastMissingBlockFileProject = {}; - - if (b) - m_LastMissingBlockFilePath = b->GetAliasedFileName().GetFullPath(); - else - m_LastMissingBlockFilePath = wxString{}; - } - - std::pair< wxString, std::shared_ptr > Marked() - { - Lock lock{ m_LastMissingBlockFileLock }; - return { m_LastMissingBlockFilePath, m_LastMissingBlockFileProject.lock() }; - } - - bool ShouldShow() - { - Lock lock{ m_LastMissingBlockFileLock }; - auto ptr = m_LastMissingBlockFileProject.lock(); - return ptr && m_missingAliasFilesWarningShouldShow; - } - - void SetShouldShow(bool b) - { - // Note that this is can be called by both the main thread and other threads. - // I don't believe we need a mutex because we are checking zero vs non-zero, - // and the setting from other threads will always be non-zero (true), and the - // setting from the main thread is always false. - m_missingAliasFilesWarningShouldShow = b; - // reset the warnings as they were probably marked by a previous run - if (m_missingAliasFilesWarningShouldShow) { - Mark( nullptr ); - } - } - -} - -// Arrange callback from low levels of block file I/O to detect missing files -static struct InstallHook{ InstallHook() { - auto hook = [](const AliasBlockFile *pAliasFile){ - if (!MissingAliasFilesDialog::ShouldShow()) - MissingAliasFilesDialog::Mark(pAliasFile); - }; - BlockFile::SetMissingAliasFileFound( hook ); -} } installHook; diff --git a/src/MissingAliasFileDialog.h b/src/MissingAliasFileDialog.h deleted file mode 100644 index ae4179721..000000000 --- a/src/MissingAliasFileDialog.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef __AUDACITY_MISSING_ALIAS_FILES_DIALOG__ -#define __AUDACITY_MISSING_ALIAS_FILES_DIALOG__ - -class AliasBlockFile; -class AudacityProject; -class TranslatableString; -class wxDialog; -class TranslatableString; - -#include -#include -#include - -namespace MissingAliasFilesDialog { - -/** \brief Mark playback as having missing aliased blockfiles - * - * Playback will continue, but the missing files will be silenced - * ShouldShow can be called to determine - * if the user should be notified - */ -void Mark(const AliasBlockFile *b); - -// Retrieve information left by Mark -std::pair< wxString, std::shared_ptr > Marked(); - -/** \brief Changes the behavior of missing aliased blockfiles warnings - */ -void SetShouldShow(bool b); - -/** \brief Returns true if the user should be notified of missing alias warnings - */ -bool ShouldShow(); - -/// Displays a custom modeless error dialog for aliased file errors -void Show(AudacityProject *parent, - const TranslatableString &dlogTitle, - const TranslatableString &message, - const wxString &helpPage, - const bool Close = true); - -wxDialog *Find( const AudacityProject &project ); - -} - -#endif diff --git a/src/Mix.h b/src/Mix.h index 43fede468..bf3aabbde 100644 --- a/src/Mix.h +++ b/src/Mix.h @@ -24,7 +24,6 @@ #include class Resample; -class DirManager; class BoundedEnvelope; class TrackFactory; class TrackList; diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index 4dfd2da02..faec011a1 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -33,7 +33,6 @@ #define ROUND(x) ((int) ((x) + 0.5)) #include "AColor.h" -#include "DirManager.h" #include "Prefs.h" #include "ProjectFileIORegistry.h" #include "prefs/ImportExportPrefs.h" @@ -124,11 +123,11 @@ static ProjectFileIORegistry::Entry registerFactory{ NoteTrack::Holder TrackFactory::NewNoteTrack() { - return std::make_shared(mDirManager); + return std::make_shared(&mProject); } -NoteTrack::NoteTrack(const std::shared_ptr &projDirManager) - : NoteTrackBase(projDirManager) +NoteTrack::NoteTrack(AudacityProject *project) + : NoteTrackBase(project) { SetDefaultName(_("Note Track")); SetName(GetDefaultName()); @@ -173,7 +172,7 @@ Alg_seq &NoteTrack::GetSeq() const Track::Holder NoteTrack::Clone() const { - auto duplicate = std::make_shared(mDirManager); + auto duplicate = std::make_shared(mProject); duplicate->Init(*this); // The duplicate begins life in serialized state. Often the duplicate is // pushed on the Undo stack. Then we want to un-serialize it (or a further @@ -464,7 +463,7 @@ Track::Holder NoteTrack::Cut(double t0, double t1) //( std::min( t1, GetEndTime() ) ) - ( std::max( t0, GetStartTime() ) ) //); - auto newTrack = std::make_shared(mDirManager); + auto newTrack = std::make_shared(mProject); newTrack->Init(*this); @@ -478,7 +477,7 @@ Track::Holder NoteTrack::Cut(double t0, double t1) //AddToDuration( delta ); // What should be done with the rest of newTrack's members? - //(mBottomNote, mDirManager, + //(mBottomNote, mProject, // mSerializationBuffer, mSerializationLength, mVisibleChannels) return newTrack; @@ -491,7 +490,7 @@ Track::Holder NoteTrack::Copy(double t0, double t1, bool) const double len = t1-t0; - auto newTrack = std::make_shared(mDirManager); + auto newTrack = std::make_shared(mProject); newTrack->Init(*this); @@ -501,7 +500,7 @@ Track::Holder NoteTrack::Copy(double t0, double t1, bool) const newTrack->SetOffset(0); // What should be done with the rest of newTrack's members? - // (mBottomNote, mDirManager, mSerializationBuffer, + // (mBottomNote, mProject, mSerializationBuffer, // mSerializationLength, mVisibleChannels) return newTrack; diff --git a/src/NoteTrack.h b/src/NoteTrack.h index 98b96c008..716d17eb6 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -46,7 +46,6 @@ SONFNS(AutoSave) class wxDC; class wxRect; -class DirManager; class Alg_seq; // from "allegro.h" using NoteTrackBase = @@ -66,7 +65,7 @@ class AUDACITY_DLL_API NoteTrack final : public NoteTrackBase { public: - NoteTrack(const std::shared_ptr &projDirManager); + NoteTrack(AudacityProject *project); virtual ~NoteTrack(); using Holder = std::shared_ptr; diff --git a/src/PlatformCompatibility.h b/src/PlatformCompatibility.h index 924972d06..4d1ce6677 100644 --- a/src/PlatformCompatibility.h +++ b/src/PlatformCompatibility.h @@ -27,7 +27,7 @@ class PlatformCompatibility public: // // On Win32, this function gets the long file name (like - // "C:\Program Files\Project.aup") from a short file name like + // "C:\Program Files\Project.aup3") from a short file name like // "C:\PROGRA~1\PROJEC~1.AUP. On other systems, the function // just returns the exact string it is given. // diff --git a/src/Project.cpp b/src/Project.cpp index f47a3a0a4..4e624fa4a 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -130,16 +130,14 @@ void AudacityProject::SetPanel( wxWindow *pPanel ) mPanel = pPanel; } -wxString AudacityProject::GetProjectName() const +const wxString &AudacityProject::GetProjectName() const { - wxString name = wxFileNameFromPath(mFileName); + return mName; +} - // Chop off the extension - size_t len = name.length(); - if (len > 4 && name.Mid(len - 4) == wxT(".aup")) - name = name.Mid(0, len - 4); - - return name; +void AudacityProject::SetProjectName(const wxString &name) +{ + mName = name; } AUDACITY_DLL_API wxFrame &GetProjectFrame( AudacityProject &project ) diff --git a/src/Project.h b/src/Project.h index 86f84cfc3..b3a34f274 100644 --- a/src/Project.h +++ b/src/Project.h @@ -24,7 +24,6 @@ class wxWindow; class AudacityProject; - AUDACITY_DLL_API AudacityProject *GetActiveProject(); // For use by ProjectManager only: extern void SetActiveProject(AudacityProject * project); @@ -120,17 +119,23 @@ class AUDACITY_DLL_API AudacityProject final const wxWindow *GetPanel() const { return mPanel; } void SetPanel( wxWindow *pPanel ); - wxString GetProjectName() const; - - const FilePath &GetFileName() { return mFileName; } - void SetFileName( const FilePath &fileName ) { mFileName = fileName; } - int GetProjectNumber(){ return mProjectNo;} - + + // Project name can be either empty or have the name of the project. + // + // If empty, it signifies that the project is empty/unmodified or + // that the project hasn't yet been saved to a permanent project + // file. + // + // If a name has been assigned, it is merely used to identify + // the project and should not be used for any other purposes. + const wxString &GetProjectName() const; + void SetProjectName(const wxString &name); + private: - // The project's name and file info - FilePath mFileName; // Note: extension-less + // The project's name + wxString mName; static int mProjectCounter;// global counter. int mProjectNo; // count when this project was created. diff --git a/src/ProjectAudioManager.cpp b/src/ProjectAudioManager.cpp index 065659af4..1956eaddb 100644 --- a/src/ProjectAudioManager.cpp +++ b/src/ProjectAudioManager.cpp @@ -19,7 +19,6 @@ Paul Licameli split from ProjectManager.cpp #include "AudioIO.h" #include "AutoRecovery.h" #include "CommonCommandFlags.h" -#include "DirManager.h" #include "LabelTrack.h" #include "Menus.h" #include "Project.h" @@ -863,7 +862,6 @@ void ProjectAudioManager::OnAudioIOStartRecording() void ProjectAudioManager::OnAudioIOStopRecording() { auto &project = mProject; - auto &dirManager = DirManager::Get( project ); auto &projectAudioIO = ProjectAudioIO::Get( project ); auto &projectFileIO = ProjectFileIO::Get( project ); auto &window = GetProjectFrame( project ); @@ -920,21 +918,11 @@ You are saving directly to a slow external storage device\n\ projectFileIO.AutoSave(); } -void ProjectAudioManager::OnAudioIONewBlockFiles( - const AutoSaveFile & blockFileLog) +void ProjectAudioManager::OnAudioIONewBlockFiles(const WaveTrackArray *tracks) { auto &project = mProject; auto &projectFileIO = ProjectFileIO::Get( project ); - // New blockfiles have been created, so add them to the auto-save file - const auto &autoSaveFileName = projectFileIO.GetAutoSaveFileName(); - if ( !autoSaveFileName.empty() ) - { - wxFFile f{ autoSaveFileName, wxT("ab") }; - if (!f.IsOpened()) - return; // Keep recording going, there's not much we can do here - blockFileLog.Append(f); - f.Close(); - } + projectFileIO.AutoSave(tracks); } void ProjectAudioManager::OnCommitRecording() diff --git a/src/ProjectAudioManager.h b/src/ProjectAudioManager.h index c8d4a4480..4e8fbeb78 100644 --- a/src/ProjectAudioManager.h +++ b/src/ProjectAudioManager.h @@ -14,6 +14,7 @@ Paul Licameli split from ProjectManager.h #include #include +#include "AudioIO.h" #include "AudioIOListener.h" // to inherit #include "ClientData.h" // to inherit @@ -136,7 +137,7 @@ private: void OnAudioIORate(int rate) override; void OnAudioIOStartRecording() override; void OnAudioIOStopRecording() override; - void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) override; + void OnAudioIONewBlockFiles(const WaveTrackArray *tracks) override; void OnCommitRecording() override; void OnSoundActivationThreshold() override; diff --git a/src/ProjectFSCK.cpp b/src/ProjectFSCK.cpp index 049c3fbc2..8b19c975d 100644 --- a/src/ProjectFSCK.cpp +++ b/src/ProjectFSCK.cpp @@ -20,7 +20,6 @@ #include "widgets/AudacityMessageBox.h" #include "Internat.h" #include "MemoryX.h" -#include "MissingAliasFileDialog.h" #include "widgets/MultiDialog.h" #include "widgets/ProgressDialog.h" @@ -34,6 +33,9 @@ int ProjectFSCK( DirManager &dm, const bool bForceError, const bool bAutoRecoverMode) { +#pragma message( "====================================================================") +#pragma message( "Don\'t forget to redo ProjectFSCK") +#pragma message( "====================================================================") // In earlier versions of this method, enumerations of errors were // all done in sequence, then the user was prompted for each type of error. // The enumerations are now interleaved with prompting, because, for example, @@ -68,7 +70,7 @@ int ProjectFSCK( else nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; } - +#if 0 FilePaths filePathArray; // *all* files in the project directory/subdirectories auto dirPath = ( dm.GetDataFilesDir() ); DirManager::RecursivelyEnumerateWithProgress( @@ -403,5 +405,6 @@ other projects. \ } MissingAliasFilesDialog::SetShouldShow(true); +#endif return nResult; } diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index 45752ae68..d364f9a9a 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -10,10 +10,10 @@ Paul Licameli split from AudacityProject.cpp #include "ProjectFileIO.h" +#include #include #include "AutoRecovery.h" -#include "DirManager.h" #include "FileNames.h" #include "Project.h" #include "ProjectFileIORegistry.h" @@ -23,9 +23,71 @@ Paul Licameli split from AudacityProject.cpp #include "WaveTrack.h" #include "widgets/AudacityMessageBox.h" #include "widgets/NumericTextCtrl.h" +#include "widgets/ProgressDialog.h" +#include "xml/XMLFileReader.h" wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent); +static const int ProjectFileID = ('A' << 24 | 'U' << 16 | 'D' << 8 | 'Y'); +static const int ProjectFileVersion = 1; +static const int ProjectFilePageSize = 4096; + +static const char *ProjectFileSchema = + "PRAGMA application_id = %d;" + "PRAGMA user_version = %d;" + "PRAGMA page_size = %d;" + "PRAGMA journal_mode = DELETE;" + "" + "CREATE TABLE IF NOT EXISTS project" + "(" + " doc TEXT" + ");" + "" + "CREATE TABLE IF NOT EXISTS autosave" + "(" + " id INTEGER PRIMARY KEY," + " dict BLOB," + " doc BLOB" + ");" + "" + "CREATE TABLE IF NOT EXISTS tags" + "(" + " name TEXT," + " value BLOB" + ");" + "" + "CREATE TABLE IF NOT EXISTS sampleblocks" + "(" + " blockid INTEGER PRIMARY KEY AUTOINCREMENT," + " sampleformat INTEGER," + " summin REAL," + " summax REAL," + " sumrms REAL," + " summary256 BLOB," + " summary64k BLOB," + " samples BLOB" + ");"; + +// This singleton handles initialization/shutdown of the SQLite library. +// It is needed because our local SQLite is built with SQLITE_OMIT_AUTOINIT +// defined. +// +// It's safe to use even if a system version of SQLite is used that didn't +// have SQLITE_OMIT_AUTOINIT defined. +class SQLiteIniter +{ +public: + SQLiteIniter() + { + sqlite3_initialize(); + } + ~SQLiteIniter() + { + sqlite3_shutdown(); + } +}; +static SQLiteIniter sqliteIniter; + static void RefreshAllTitles(bool bShowProjectNumbers ) { for ( auto pProject : AllProjects{} ) { @@ -85,16 +147,397 @@ const ProjectFileIO &ProjectFileIO::Get( const AudacityProject &project ) return Get( const_cast< AudacityProject & >( project ) ); } -// PRL: I preserve this handler function for an event that was never sent, but -// I don't know the intention. - -ProjectFileIO::ProjectFileIO( AudacityProject &project ) - : mProject{ project } +ProjectFileIO::ProjectFileIO(AudacityProject &project) +: mProject(project) { + mDB = nullptr; + + mRecovered = false; + mModified = false; + mTemporary = true; + UpdatePrefs(); } -ProjectFileIO::~ProjectFileIO() = default; +ProjectFileIO::~ProjectFileIO() +{ + if (mDB) + { + // Not much we can do if this fails. The user will simply get + // the recovery dialog upon next restart. + if (CloseDB()) + { + // Always remove the journal now that the DB is closed + wxRemoveFile(mDBPath + wxT("-journal")); + + // At this point, we are shutting down cleanly and if the project file is + // still in the temp directory it means that the user has chosen not to + // save it. So, delete it. + if (mTemporary && !mDBPath.empty()) + { + wxFileName temp(FileNames::TempDir()); + if (temp == wxPathOnly(mDBPath)) + { + wxRemoveFile(mDBPath); + } + } + } + } +} + +sqlite3 *ProjectFileIO::DB() +{ + if (mDB) + { + return mDB; + } + + return OpenDB(); +} + +sqlite3 *ProjectFileIO::OpenDB(FilePath fileName) +{ + wxASSERT(mDB == nullptr); + + if (fileName.empty()) + { + fileName = GetFileName(); + if (fileName.empty()) + { + fileName = FileNames::UnsavedProjectFileName(); + mTemporary = true; + } + else + { + mTemporary = false; + } + } + + int rc = sqlite3_open(fileName, &mDB); + if (rc != SQLITE_OK) + { + // AUD3 TODO COMPLAIN AND THROW - crash in inevitable otherwise + return nullptr; + } + + // AUD3 TODO get rid of the mDBPath?!?!?!?! + mDBPath = fileName; + mFileName = mDBPath; + + if (!CheckVersion()) + { + CloseDB(); + + // AUD3 TODO COMPLAIN AND THROW - crash in inevitable otherwise + return nullptr; + } + + return mDB; +} + +bool ProjectFileIO::CloseDB() +{ + int rc; + + if (mDB) + { + rc = sqlite3_close(mDB); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mDB)); + } + else + { + wxRemoveFile(mDBPath + wxT("-journal")); + } + + mDB = nullptr; + } + + return true; +} + +bool ProjectFileIO::DeleteDB() +{ + if (!mDBPath.empty() && IsTemporary()) + { + wxFileName temp(FileNames::TempDir()); + if (temp == wxPathOnly(mDBPath)) + { + if (!wxRemoveFile(mDBPath)) + { + return false; + } + + wxRemoveFile(mDBPath + wxT("-journal")); + } + } + + return true; +} + +bool ProjectFileIO::CleanDB() +{ + auto db = DB(); + int rc; + + AutoSave(); + + wxString destpath = wxFileName::CreateTempFileName(mDBPath + ".xxxxx"); + + if (CopyTo(destpath)) + { + if (CloseDB()) + { + // This can be removed even if we fail below since the DB is closed + wxRemoveFile(mDBPath + wxT("-journal")); + + wxString tmppath = wxFileName::CreateTempFileName(mDBPath + ".xxxxx"); + if (wxRename(mDBPath, tmppath) == 0) + { + if (wxRename(destpath, mDBPath) == 0) + { + wxRemoveFile(tmppath); + + // Success + return true; + } + + if (wxRename(tmppath, mDBPath) == 0) + { + wxRemoveFile(destpath); + } + } + } + } + + // AUD3 TODO COMPILAIN + return false; +} + +/* static */ +int ProjectFileIO::ExecCallback(void *data, int cols, char **vals, char **names) +{ + ExecParm *parms = static_cast(data); + return parms->func(parms->result, cols, vals, names); +} + +int ProjectFileIO::Exec(const char *query, ExecCB callback, wxString *result) +{ + char *errmsg = nullptr; + ExecParm ep = {callback, result}; + + int rc = sqlite3_exec(DB(), query, ExecCallback, &ep, &errmsg); + + if (errmsg) + { + mLastError.Format(XO("SQLite Error: %s"), wxString(errmsg)); + sqlite3_free(errmsg); + } + + return rc; +} + +wxString ProjectFileIO::GetValue(const char *sql) +{ + auto getresult = [&](wxString *result, int cols, char **vals, char **names) + { + if (cols == 1 && vals[0]) + { + result->append(vals[0]); + return SQLITE_OK; + } + + return SQLITE_ABORT; + }; + + wxString value; + + int rc = Exec(sql, getresult, &value); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), mLastError.Translation()); + } + + return value; +} + +bool ProjectFileIO::GetBlob(const char *sql, wxMemoryBuffer &buffer) +{ + auto db = DB(); + int rc; + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + const void *blob = sqlite3_column_blob(stmt, 0); + int size = sqlite3_column_bytes(stmt, 0); + + buffer.AppendData(blob, size); + + return true; +} + +bool ProjectFileIO::CheckVersion() +{ + auto db = DB(); + int rc; + + // Install our schema if this is an empty DB + long count = -1; + GetValue("SELECT Count(*) FROM sqlite_master WHERE type='table';").ToLong(&count); + if (count == 0) + { + return InstallSchema(); + } + + // Check for our application ID + long appid = 0; + GetValue("PRAGMA application_ID;").ToLong(&appid); + if (appid != ProjectFileID) + { + mLastError = XO("This is not an Audacity AUP3 file"); + return false; + } + + // Get the project file version + long version; + GetValue("PRAGMA user_version;").ToLong(&version); + + // Project file version is higher than ours. We will refuse + // to process it since we can't trust anything about it. + if (version > ProjectFileVersion) + { + // AUD3 - complain about too new schema + // can't handle it! + return false; + } + + // Project file is older than ours, ask the user if it's okay to + // upgrade. + if (version < ProjectFileVersion) + { + return UpgradeSchema(); + } + + return true; +} + +bool ProjectFileIO::InstallSchema() +{ + auto db = DB(); + int rc; + + char sql[1024]; + sqlite3_snprintf(sizeof(sql), + sql, + ProjectFileSchema, + ProjectFileID, + ProjectFileVersion, + ProjectFilePageSize); + + rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + return false; + } + + return true; +} + +bool ProjectFileIO::UpgradeSchema() +{ + return true; +} + +bool ProjectFileIO::CopyTo(const FilePath &destpath) +{ + auto db = DB(); + int rc; + bool success = true; + ProgressResult res = ProgressResult::Success; + + sqlite3 *destdb = nullptr; + + /* Open the database file identified by zFilename. */ + rc = sqlite3_open(destpath, &destdb); + if (rc == SQLITE_OK) + { + sqlite3_backup *backup = sqlite3_backup_init(destdb, "main", db, "main"); + if (backup) + { + /* i18n-hint: This title appears on a dialog that indicates the progress + in doing something.*/ + ProgressDialog progress(XO("Progress"), XO("Saving project")); + + do + { + int remaining = sqlite3_backup_remaining(backup); + int total = sqlite3_backup_pagecount(backup); + wxLogDebug(wxT("remaining %d total %d"), remaining, total); + + if (progress.Update(total - remaining, total) != ProgressResult::Success) + { + success = false; + break; + } + + rc = sqlite3_backup_step(backup, 12); + } while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); + + // The return code from finish() will reflect errors from step() + rc = sqlite3_backup_finish(backup); + if (rc != SQLITE_OK) + { + success = false; + } + } + + // Close the DB + rc = sqlite3_close(destdb); + if (rc != SQLITE_OK) + { + success = false; + } + + // Always delete the journal + wxRemoveFile(destpath + wxT("-journal")); + + if (!success) + { + wxRemoveFile(destpath); + } + } + else + { + success = false; + } + + return success; +} void ProjectFileIO::UpdatePrefs() { @@ -102,37 +545,40 @@ void ProjectFileIO::UpdatePrefs() } // Pass a number in to show project number, or -1 not to. -void ProjectFileIO::SetProjectTitle( int number) +void ProjectFileIO::SetProjectTitle(int number) { auto &project = mProject; auto pWindow = project.GetFrame(); - if ( !pWindow ) + if (!pWindow) + { return; + } auto &window = *pWindow; wxString name = project.GetProjectName(); // If we are showing project numbers, then we also explicitly show "" if there // is none. - if( number >= 0 ){ + if (number >= 0) + { /* i18n-hint: The %02i is the project number, the %s is the project name.*/ - name = wxString::Format( _("[Project %02i] Audacity \"%s\""), number+1 , - name.empty() ? "" : (const char *)name ); + name = wxString::Format(_("[Project %02i] Audacity \"%s\""), number + 1, + name.empty() ? "" : (const char *)name); } // If we are not showing numbers, then shows as 'Audacity'. - else if( name.empty() ) + else if (name.empty()) { - SetLoadedFromAup( false ); name = _TS("Audacity"); } - if ( IsRecovered() ) + if (mRecovered) { name += wxT(" "); /* i18n-hint: E.g this is recovered audio that had been lost.*/ name += _("(Recovered)"); } - if ( name != window.GetTitle() ) { + if (name != window.GetTitle()) + { window.SetTitle( name ); window.SetName(name); // to make the nvda screen reader read the correct title @@ -141,6 +587,27 @@ void ProjectFileIO::SetProjectTitle( int number) } } +const FilePath &ProjectFileIO::GetFileName() const +{ + wxASSERT(mFileName == mDBPath); + return mFileName; +} + +void ProjectFileIO::SetFileName(const FilePath &fileName) +{ + mFileName = fileName; + if (mFileName.empty()) + { + mProject.SetProjectName({}); + } + else + { + mProject.SetProjectName(wxFileName(mFileName).GetName()); + } + + SetProjectTitle(); +} + // Most of this string was duplicated 3 places. Made the warning consistent in this global. // The %s is to be filled with the version string. // PRL: Do not statically allocate a string in _() ! @@ -172,246 +639,144 @@ bool ProjectFileIO::WarnOfLegacyFile( ) bool ProjectFileIO::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { auto &project = mProject; - auto &window = GetProjectFrame( project ); - auto &viewInfo = ViewInfo::Get( project ); - auto &dirManager = DirManager::Get( project ); - auto &settings = ProjectSettings::Get( project ); - bool bFileVersionFound = false; + auto &window = GetProjectFrame(project); + auto &viewInfo = ViewInfo::Get(project); + auto &settings = ProjectSettings::Get(project); + wxString fileVersion; - wxString audacityVersion = _(""); + wxString audacityVersion; int requiredTags = 0; long longVpos = 0; - // The auto-save data dir the project has been recovered from - FilePath recoveryAutoSaveDataDir; - // loop through attrs, which is a null-terminated list of // attribute-value pairs - while(*attrs) { + while (*attrs) + { const wxChar *attr = *attrs++; const wxChar *value = *attrs++; if (!value || !XMLValueChecker::IsGoodString(value)) + { break; + } - if (viewInfo.ReadXMLAttribute(attr, value)) { + if (viewInfo.ReadXMLAttribute(attr, value)) + { // We need to save vpos now and restore it below longVpos = std::max(longVpos, long(viewInfo.vpos)); continue; } - if (!wxStrcmp(attr, wxT("datadir"))) - { - // - // This is an auto-saved version whose data is in another directory - // - // Note: This attribute must currently be written and parsed before - // any other attributes - // - if ((value[0] != 0) && XMLValueChecker::IsGoodPathString(value)) - { - // Remember that this is a recovered project - mIsRecovered = true; - recoveryAutoSaveDataDir = value; - } - } - else if (!wxStrcmp(attr, wxT("version"))) { fileVersion = value; - bFileVersionFound = true; requiredTags++; } - else if (!wxStrcmp(attr, wxT("audacityversion"))) { + else if (!wxStrcmp(attr, wxT("audacityversion"))) + { audacityVersion = value; requiredTags++; } - else if (!wxStrcmp(attr, wxT("projname"))) { - FilePath projName; - FilePath projPath; - - if (mIsRecovered) { - // Fake the filename as if we had opened the original file - // (which was lost by the crash) rather than the one in the - // auto save folder - wxFileName realFileDir; - realFileDir.AssignDir(recoveryAutoSaveDataDir); - realFileDir.RemoveLastDir(); - - wxString realFileName = value; - if (realFileName.length() >= 5 && - realFileName.Right(5) == wxT("_data")) - { - realFileName = realFileName.Left(realFileName.length() - 5); - } - - if (realFileName.empty()) - { - // A previously unsaved project has been recovered, so fake - // an unsaved project. The data files just stay in the temp - // directory - dirManager.SetLocalTempDir(recoveryAutoSaveDataDir); - project.SetFileName( wxT("") ); - projName = wxT(""); - projPath = wxT(""); - } - else - { - realFileName += wxT(".aup"); - projPath = realFileDir.GetFullPath(); - project.SetFileName( - wxFileName{ projPath, realFileName }.GetFullPath() ); - mbLoadedFromAup = true; - projName = value; - } - - SetProjectTitle(); - } - else { - projName = value; - projPath = wxPathOnly( project.GetFileName() ); - } - - if (!projName.empty()) - { - // First try to load the data files based on the _data dir given in the .aup file - // If this fails then try to use the filename of the .aup as the base directory - // This is because unzipped projects e.g. those that get transfered between mac-pc - // have encoding issues and end up expanding the wrong filenames for certain - // international characters (such as capital 'A' with an umlaut.) - if (!dirManager.SetProject(projPath, projName, false)) - { - projName = project.GetProjectName() + wxT("_data"); - if (!dirManager.SetProject(projPath, projName, false)) { - AudacityMessageBox( - XO("Couldn't find the project data folder: \"%s\"") - .Format( projName ), - XO("Error Opening Project"), - wxOK | wxCENTRE, - &window); - return false; - } - } - } - - requiredTags++; - } - - else if (!wxStrcmp(attr, wxT("rate"))) { + else if (!wxStrcmp(attr, wxT("rate"))) + { double rate; Internat::CompatibleToDouble(value, &rate); settings.SetRate( rate ); } - else if (!wxStrcmp(attr, wxT("snapto"))) { + else if (!wxStrcmp(attr, wxT("snapto"))) + { settings.SetSnapTo(wxString(value) == wxT("on") ? true : false); } else if (!wxStrcmp(attr, wxT("selectionformat"))) + { settings.SetSelectionFormat( NumericConverter::LookupFormat( NumericConverter::TIME, value) ); + } else if (!wxStrcmp(attr, wxT("audiotimeformat"))) + { settings.SetAudioTimeFormat( NumericConverter::LookupFormat( NumericConverter::TIME, value) ); + } else if (!wxStrcmp(attr, wxT("frequencyformat"))) + { settings.SetFrequencySelectionFormatName( NumericConverter::LookupFormat( NumericConverter::FREQUENCY, value ) ); + } else if (!wxStrcmp(attr, wxT("bandwidthformat"))) + { settings.SetBandwidthSelectionFormatName( NumericConverter::LookupFormat( NumericConverter::BANDWIDTH, value ) ); + } } // while - if (longVpos != 0) { + if (longVpos != 0) + { // PRL: It seems this must happen after SetSnapTo - viewInfo.vpos = longVpos; + viewInfo.vpos = longVpos; } - // Specifically detect newer versions of Audacity - // WARNING: This will need review/revision if we ever have a version string - // such as 1.5.10, i.e. with 2 digit numbers. - // We're able to do a shortcut and use string comparison because we know - // that does not happen. - // TODO: Um. We actually have released 0.98 and 1.3.14 so the comment - // above is inaccurate. + if (requiredTags < 2) + { + return false; + } - if (!bFileVersionFound || - (fileVersion.length() != 5) || // expecting '1.1.0', for example - // JKC: I commented out next line. IsGoodInt is not for - // checking dotted numbers. - //!XMLValueChecker::IsGoodInt(fileVersion) || - (fileVersion > wxT(AUDACITY_FILE_FORMAT_VERSION))) + // Parse the file version from the project + int fver; + int frel; + int frev; + if (!wxSscanf(fileVersion, wxT("%i.%i.%i"), &fver, &frel, &frev)) + { + return false; + } + + // Parse the file version Audacity was build with + int cver; + int crel; + int crev; + wxSscanf(wxT(AUDACITY_FILE_FORMAT_VERSION), wxT("%i.%i.%i"), &cver, &crel, &crev); + + if (cver < fver || crel < frel || crev < frev) { /* i18n-hint: %s will be replaced by the version number.*/ auto msg = XO("This file was saved using Audacity %s.\nYou are using Audacity %s. You may need to upgrade to a newer version to open this file.") - .Format(audacityVersion, - AUDACITY_VERSION_STRING); + .Format(audacityVersion, AUDACITY_VERSION_STRING); + AudacityMessageBox( msg, XO("Can't open project file"), wxOK | wxICON_EXCLAMATION | wxCENTRE, &window); + return false; } - // NOTE: It looks as if there was some confusion about fileversion and audacityversion. - // fileversion NOT being increased when file formats changed, so unfortunately we're - // using audacityversion to rescue the situation. - - // KLUDGE: guess the true 'fileversion' by stripping away any '-beta-Rc' stuff on - // audacityVersion. - // It's fairly safe to do this as it has already been established that the - // supported file version was five chars long. - fileVersion = audacityVersion.Mid(0,5); - - bool bIsOld = fileVersion < wxT(AUDACITY_FILE_FORMAT_VERSION); - bool bIsVeryOld = fileVersion < wxT("1.1.9" ); - // Very old file versions could even have the file version starting - // with text: 'AudacityProject Version 0.95' - // Atoi return zero in this case. - bIsVeryOld |= wxAtoi( fileVersion )==0; - // Specifically detect older versions of Audacity - if ( bIsOld | bIsVeryOld ) { - auto msg = gsLegacyFileWarning().Format( audacityVersion ); - - int icon_choice = wxICON_EXCLAMATION; - if( bIsVeryOld ) - // Stop icon, and choose 'NO' by default. - icon_choice = wxICON_STOP | wxNO_DEFAULT; - int action = AudacityMessageBox( - msg, - XO("Warning - Opening Old Project File"), - wxYES_NO | icon_choice | wxCENTRE, - &window); - if (action == wxNO) - return false; - } - if (wxStrcmp(tag, wxT("audacityproject")) && - wxStrcmp(tag, wxT("project"))) { + wxStrcmp(tag, wxT("project"))) + { // If the tag name is not one of these two (the NEW name is // "project" with an Audacity namespace, but we don't detect // the namespace yet), then we don't know what the error is return false; } - if (requiredTags < 3) - return false; - // All other tests passed, so we succeed return true; } XMLTagHandler *ProjectFileIO::HandleXMLChild(const wxChar *tag) { - auto &project = mProject; - auto fn = ProjectFileIORegistry::Lookup( tag ); + auto fn = ProjectFileIORegistry::Lookup(tag); if (fn) - return fn( project ); + { + return fn(mProject); + } return nullptr; } @@ -431,49 +796,20 @@ void ProjectFileIO::WriteXMLHeader(XMLWriter &xmlFile) const xmlFile.Write(wxT(">\n")); } -void ProjectFileIO::WriteXML( - XMLWriter &xmlFile, FilePaths *strOtherNamesArray) +void ProjectFileIO::WriteXML(XMLWriter &xmlFile, const WaveTrackArray *tracks) // may throw { auto &proj = mProject; - auto &tracks = TrackList::Get( proj ); - auto &viewInfo = ViewInfo::Get( proj ); - auto &dirManager = DirManager::Get( proj ); - auto &tags = Tags::Get( proj ); - const auto &settings = ProjectSettings::Get( proj ); - - bool bWantSaveCopy = (strOtherNamesArray != nullptr); + auto &tracklist = TrackList::Get(proj); + auto &viewInfo = ViewInfo::Get(proj); + auto &tags = Tags::Get(proj); + const auto &settings = ProjectSettings::Get(proj); //TIMER_START( "AudacityProject::WriteXML", xml_writer_timer ); - // Warning: This block of code is duplicated in Save, for now... - wxFileName project { proj.GetFileName() }; - if (project.GetExt() == wxT("aup")) - project.SetExt( {} ); - auto projName = project.GetFullName() + wxT("_data"); - // End Warning -DMM xmlFile.StartTag(wxT("project")); xmlFile.WriteAttr(wxT("xmlns"), wxT("http://audacity.sourceforge.net/xml/")); - if (mAutoSaving) - { - // - // When auto-saving, remember full path to data files directory - // - // Note: This attribute must currently be written and parsed before - // all other attributes - // - xmlFile.WriteAttr(wxT("datadir"), dirManager.GetDataFilesDir()); - - // Note that the code at the start assumes that if mFileName has a value - // then the file has been saved. This is not necessarily true when - // autosaving as it gets set by AddImportedTracks (presumably as a proposal). - // I don't think that mDirManager.projName gets set without a save so check that. - if( !IsProjectSaved() ) - projName = wxT("_data"); - } - - xmlFile.WriteAttr(wxT("projname"), projName); xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION)); xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING); @@ -490,187 +826,282 @@ void ProjectFileIO::WriteXML( tags.WriteXML(xmlFile); unsigned int ndx = 0; - tracks.Any().Visit( - [&](WaveTrack *pWaveTrack) { - if (bWantSaveCopy) { - if (!pWaveTrack->IsLeader()) - return; - - //vvv This should probably be a method, WaveTrack::WriteCompressedTrackXML(). - xmlFile.StartTag(wxT("import")); - xmlFile.WriteAttr(wxT("filename"), - (*strOtherNamesArray)[ndx]); // Assumes mTracks order hasn't changed! - - // Don't store "channel" and "linked" tags because the importer can figure that out, - // e.g., from stereo Ogg files. - // xmlFile.WriteAttr(wxT("channel"), t->GetChannel()); - // xmlFile.WriteAttr(wxT("linked"), t->GetLinked()); - - const auto offset = - TrackList::Channels( pWaveTrack ).min( &WaveTrack::GetOffset ); - xmlFile.WriteAttr(wxT("offset"), offset, 8); - xmlFile.WriteAttr(wxT("mute"), pWaveTrack->GetMute()); - xmlFile.WriteAttr(wxT("solo"), pWaveTrack->GetSolo()); - pWaveTrack->Track::WriteCommonXMLAttributes( xmlFile, false ); - - // Don't store "rate" tag because the importer can figure that out. - // xmlFile.WriteAttr(wxT("rate"), pWaveTrack->GetRate()); - xmlFile.WriteAttr(wxT("gain"), (double)pWaveTrack->GetGain()); - xmlFile.WriteAttr(wxT("pan"), (double)pWaveTrack->GetPan()); - xmlFile.EndTag(wxT("import")); - - ndx++; - } - else { - pWaveTrack->SetAutoSaveIdent(mAutoSaving ? ++ndx : 0); - pWaveTrack->WriteXML(xmlFile); - } - }, - [&](Track *t) { - t->WriteXML(xmlFile); - } - ); - - if (!mAutoSaving) + if (tracks) { - // Only write closing bracket when not auto-saving, since we may add - // recording log data to the end of the file later - xmlFile.EndTag(wxT("project")); - } - //TIMER_STOP( xml_writer_timer ); - -} - -static wxString CreateUniqueName() -{ - static int count = 0; - return wxDateTime::Now().Format(wxT("%Y-%m-%d %H-%M-%S")) + - wxString::Format(wxT(" N-%i"), ++count); -} - -// -// This small template class resembles a try-finally block -// -// It sets var to val_entry in the constructor and -// var to val_exit in the destructor. -// -template -class VarSetter -{ -public: - VarSetter(T* var, T val_entry, T val_exit) - { - mVar = var; - mValExit = val_exit; - *var = val_entry; - } - - ~VarSetter() - { - *mVar = mValExit; - } -private: - T* mVar; - T mValExit; -}; - -void ProjectFileIO::AutoSave() -{ - auto &project = mProject; - auto &window = GetProjectFrame( project ); - // SonifyBeginAutoSave(); // part of RBD's r10680 stuff now backed out - - // To minimize the possibility of race conditions, we first write to a - // file with the extension ".tmp", then rename the file to .autosave - wxString projName; - - auto fileName = project.GetFileName(); - if (fileName.empty()) - projName = wxT("New Project"); - else - projName = wxFileName{ fileName }.GetName(); - - wxString fn = wxFileName(FileNames::AutoSaveDir(), - projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath(); - - // PRL: I found a try-catch and rewrote it, - // but this guard is unnecessary because AutoSaveFile does not throw - bool success = GuardedCall< bool >( [&] - { - VarSetter setter(&mAutoSaving, true, false); - - AutoSaveFile buffer; - WriteXMLHeader( buffer ); - WriteXML( buffer, nullptr ); - - wxFFile saveFile; - saveFile.Open(fn + wxT(".tmp"), wxT("wb")); - return buffer.Write(saveFile); - } ); - - if (!success) - return; - - // Now that we have a NEW auto-save file, DELETE the old one - DeleteCurrentAutoSaveFile(); - - if (!mAutoSaveFileName.empty()) - return; // could not remove auto-save file - - if (!wxRenameFile(fn + wxT(".tmp"), fn + wxT(".autosave"))) - { - AudacityMessageBox( - XO("Could not create autosave file: %s") - .Format( fn + wxT(".autosave") ), - XO("Error"), - wxICON_STOP, - &window); - return; - } - - mAutoSaveFileName += fn + wxT(".autosave"); - // no-op cruft that's not #ifdefed for NoteTrack - // See above for further comments. - // SonifyEndAutoSave(); -} - -void ProjectFileIO::DeleteCurrentAutoSaveFile() -{ - auto &project = mProject; - auto &window = GetProjectFrame( project ); - if (!mAutoSaveFileName.empty()) - { - if (wxFileExists(mAutoSaveFileName)) + for (auto track : *tracks) { - if (!wxRemoveFile(mAutoSaveFileName)) - { - AudacityMessageBox( - XO("Could not remove old autosave file: %s") - .Format( mAutoSaveFileName ), - XO("Error"), - wxICON_STOP, - &window); - return; - } + track->WriteXML(xmlFile); } - - mAutoSaveFileName = wxT(""); } + else + { + tracklist.Any().Visit([&](Track *t) + { + t->WriteXML(xmlFile); + }); + } + + xmlFile.EndTag(wxT("project")); + + //TIMER_STOP( xml_writer_timer ); } -bool ProjectFileIO::IsProjectSaved() const { - auto &project = mProject; - auto &dirManager = DirManager::Get( project ); - // This is true if a project was opened from an .aup - // Otherwise it becomes true only when a project is first saved successfully - // in DirManager::SetProject - return (!dirManager.GetProjectName().empty()); +bool ProjectFileIO::AutoSave(const WaveTrackArray *tracks) +{ + AutoSaveFile autosave; + WriteXMLHeader(autosave); + WriteXML(autosave, tracks); + + return AutoSave(autosave); +} + +bool ProjectFileIO::AutoSave(const AutoSaveFile &autosave) +{ + auto db = DB(); + int rc; + + mModified = true; + + // For now, we always use an ID of 1. This will replace the previously + // writen row every time. + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "INSERT INTO autosave(id, dict, doc) VALUES(1, ?1, ?2)" + " ON CONFLICT(id) DO UPDATE SET %sdoc = ?2;", + autosave.DictChanged() ? "dict = ?1, " : ""); + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + const wxMemoryBuffer &dict = autosave.GetDict(); + const wxMemoryBuffer &data = autosave.GetData(); + + sqlite3_bind_blob(stmt, 1, dict.GetData(), dict.GetDataLen(), SQLITE_STATIC); + sqlite3_bind_blob(stmt, 2, data.GetData(), data.GetDataLen(), SQLITE_STATIC); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + return true; +} + +bool ProjectFileIO::AutoSaveDelete() +{ + auto db = DB(); + int rc; + + rc = sqlite3_exec(db, "DELETE FROM autosave;", nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + return true; +} + +bool ProjectFileIO::LoadProject(const FilePath &fileName) +{ + // Open the project file + OpenDB(fileName); + + // Get the XML document...either from the project or autosave + wxString doc; + + // Get the autosave doc, if any + bool wasAutosave = false; + wxMemoryBuffer buffer; + if (GetBlob("SELECT dict || doc FROM autosave WHERE id = 1;", buffer)) + { + doc = AutoSaveFile::Decode(buffer); + wasAutosave = true; + } + else + { + // Get the project doc + doc = GetValue("SELECT doc FROM project;"); + } + + if (doc.empty()) + { + // AUD3 fixme + wxLogDebug(wxT("%s"), mLastError.Translation()); + // handle error + return false; + } + + XMLFileReader xmlFile; + + bool success = xmlFile.ParseString(this, doc); + if (!success) + { + mLastError = xmlFile.GetErrorStr(); + } + else + { + mRecovered = wasAutosave; + SetFileName(fileName); + SetProjectTitle(); + } + + if (mRecovered) + { + mModified = true; + } + + return success; +} + +bool ProjectFileIO::SaveProject(const FilePath &fileName) +{ + auto db = DB(); + int rc; + + XMLStringWriter doc; + WriteXMLHeader(doc); + WriteXML(doc); + + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "REPLACE INTO project (doc) VALUES(?);"); + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + sqlite3_bind_text(stmt, 1, doc, -1, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + // handle error + return false; + } + + sqlite3_finalize(stmt); + stmt = nullptr; + + if (mDBPath != fileName) + { + if (!CopyTo(fileName)) + { + return false; + } + + // Remember the original project filename + wxString origname = mDBPath; + + // Close the original project file + CloseDB(); + + // Set the new filename + SetFileName(fileName); + + // And open the new one + OpenDB(fileName); + + // If the original project was temporary, then delete it now + if (mTemporary) + { + wxRemoveFile(origname); + } + } + + AutoSaveDelete(); + + // No longer modified + mModified = false; + + // No longer recovered + mRecovered = false; + + // No longer a temporary project + mTemporary = false; + + // Adjust the title + SetProjectTitle(); + + return true; +} + +bool ProjectFileIO::IsModified() const +{ + return mModified; +} + +bool ProjectFileIO::IsTemporary() const +{ + return mTemporary; +} + +bool ProjectFileIO::IsRecovered() const +{ + return mRecovered; } void ProjectFileIO::Reset() { - mProject.SetFileName( {} ); - mIsRecovered = false; - mbLoadedFromAup = false; + wxASSERT_MSG(mDB == nullptr, wxT("Resetting project with open project file")); + + mModified = false; + mRecovered = false; + + SetFileName({}); SetProjectTitle(); } + +wxLongLong ProjectFileIO::GetFreeDiskSpace() +{ + // make sure it's open and the path is defined + auto db = DB(); + + wxLongLong freeSpace; + if (wxGetDiskSpace(wxPathOnly(mDBPath), NULL, &freeSpace)) + { + return freeSpace; + } + + return -1; +} + +const TranslatableString & ProjectFileIO::GetLastError() const +{ + return mLastError; +} + diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index d5be00f26..5dc02169b 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -15,7 +15,14 @@ Paul Licameli split from AudacityProject.h #include "Prefs.h" // to inherit #include "xml/XMLTagHandler.h" // to inherit +#include + class AudacityProject; +class AutoSaveFile; +class SampleBlock; +class WaveTrack; + +using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >; ///\brief Object associated with a project that manages reading and writing /// of Audacity project file formats, and autosave @@ -35,33 +42,35 @@ public: bool WarnOfLegacyFile( ); - const FilePath &GetAutoSaveFileName() { return mAutoSaveFileName; } - // It seems odd to put this method in this class, but the results do depend // on what is discovered while opening the file, such as whether it is a // recovery file - void SetProjectTitle( int number = -1 ); + void SetProjectTitle(int number = -1); + // Should be empty or a fully qualified file name - bool IsProjectSaved() const; + const FilePath &GetFileName() const; + void SetFileName( const FilePath &fileName ); + + bool IsModified() const; + bool IsTemporary() const; + bool IsRecovered() const; void Reset(); - - void AutoSave(); - void DeleteCurrentAutoSaveFile(); - bool IsRecovered() const { return mIsRecovered; } - void SetIsRecovered( bool value ) { mIsRecovered = value; } - bool IsLoadedFromAup() const { return mbLoadedFromAup; } - void SetLoadedFromAup( bool value ) { mbLoadedFromAup = value; } - + bool AutoSave(const WaveTrackArray *tracks = nullptr); + bool AutoSave(const AutoSaveFile &autosave); + bool AutoSaveDelete(); + + bool LoadProject(const FilePath &fileName); + bool SaveProject(const FilePath &fileName); + XMLTagHandler *HandleXMLChild(const wxChar *tag) override; void WriteXMLHeader(XMLWriter &xmlFile) const; + void WriteXML(XMLWriter &xmlFile, const WaveTrackArray *tracks = nullptr) /* not override */; - // If the second argument is not null, that means we are saving a - // compressed project, and the wave tracks have been exported into the - // named files - void WriteXML( - XMLWriter &xmlFile, FilePaths *strOtherNamesArray) /* not override */; + wxLongLong GetFreeDiskSpace(); + + const TranslatableString & GetLastError() const; private: // XMLTagHandler callback methods @@ -69,19 +78,52 @@ private: void UpdatePrefs() override; + using ExecCB = std::function; + using ExecFunc = int (*)(void *data, int cols, char **vals, char **names); + struct ExecParm + { + ExecCB func; + wxString *result; + }; + static int ExecCallback(void *data, int cols, char **vals, char **names); + int Exec(const char *query, ExecCB callback, wxString *result); + + sqlite3 *DB(); + sqlite3 *OpenDB(FilePath fileName = {}); + bool CloseDB(); + bool DeleteDB(); + bool CleanDB(); + + wxString GetValue(const char *sql); + bool GetBlob(const char *sql, wxMemoryBuffer &buffer); + + bool CheckVersion(); + bool InstallSchema(); + bool UpgradeSchema(); + + bool CopyTo(const FilePath &destpath); + +private: // non-static data members AudacityProject &mProject; - // Last auto-save file name and path (empty if none) - FilePath mAutoSaveFileName; - - // Are we currently auto-saving or not? - bool mAutoSaving{ false }; + // The project's file path + FilePath mFileName; // Has this project been recovered from an auto-saved version - bool mIsRecovered{ false }; + bool mRecovered; - bool mbLoadedFromAup{ false }; + // Has this project been modified + bool mModified; + + // Is this project still a temporary/unsaved project + bool mTemporary; + + sqlite3 *mDB; + FilePath mDBPath; + TranslatableString mLastError; + + friend SampleBlock; }; class wxTopLevelWindow; diff --git a/src/ProjectFileManager.cpp b/src/ProjectFileManager.cpp index bbef85b29..47a1c33f0 100644 --- a/src/ProjectFileManager.cpp +++ b/src/ProjectFileManager.cpp @@ -21,7 +21,6 @@ Paul Licameli split from AudacityProject.cpp #include #include "AutoRecovery.h" #include "Dependencies.h" -#include "DirManager.h" #include "FileFormats.h" #include "FileNames.h" #include "Legacy.h" @@ -136,50 +135,25 @@ auto ProjectFileManager::ReadProjectFile( const FilePath &fileName ) auto &projectFileIO = ProjectFileIO::Get( project ); auto &window = GetProjectFrame( project ); - project.SetFileName( fileName ); - projectFileIO.SetLoadedFromAup( true ); - projectFileIO.SetIsRecovered( false ); - projectFileIO.SetProjectTitle(); - - const wxString autoSaveExt = wxT("autosave"); - if ( wxFileNameWrapper{ fileName }.GetExt() == autoSaveExt ) - { - AutoSaveFile asf; - if (!asf.Decode(fileName)) - { - auto message = AutoSaveFile::FailureMessage( fileName ); - AudacityMessageBox( - message, - XO("Error decoding file"), - wxOK | wxCENTRE, - &window ); - // Important: Prevent deleting any temporary files! - DirManager::SetDontDeleteTempFiles(); - return { true }; - } - } - /// /// Parse project file /// - - XMLFileReader xmlFile; - - bool bParseSuccess = xmlFile.Parse(&projectFileIO, fileName); + bool bParseSuccess = projectFileIO.LoadProject(fileName); bool err = false; - if (bParseSuccess) { + if (bParseSuccess) + { // By making a duplicate set of pointers to the existing blocks // on disk, we add one to their reference count, guaranteeing // that their reference counts will never reach zero and thus // the version saved on disk will be preserved until the // user selects Save(). - mLastSavedTracks = TrackList::Create( nullptr ); auto &tracks = TrackList::Get( project ); - for (auto t : tracks.Any()) { + for (auto t : tracks.Any()) + { if (t->GetErrorOpening()) { wxLogWarning( @@ -194,21 +168,24 @@ auto ProjectFileManager::ReadProjectFile( const FilePath &fileName ) } } + // AUD3 - FIXME - error messages - needed????? return { - false, bParseSuccess, err, xmlFile.GetErrorStr(), - FindHelpUrl( xmlFile.GetLibraryErrorStr() ) + false, bParseSuccess, err, projectFileIO.GetLastError(), + FindHelpUrl( projectFileIO.GetLastError() ) }; } bool ProjectFileManager::Save() { + auto &projectFileIO = ProjectFileIO::Get(mProject); + // Prompt for file name? - bool bPromptingRequired = !ProjectFileIO::Get( mProject ).IsProjectSaved(); - - if (bPromptingRequired) + if (projectFileIO.IsModified()) + { return SaveAs(); + } - return DoSave(false, false); + return DoSave(projectFileIO.GetFileName(), false); } #if 0 @@ -242,77 +219,62 @@ private: }; #endif -// Assumes AudacityProject::mFileName has been set to the desired path. -bool ProjectFileManager::DoSave (const bool fromSaveAs, - const bool bWantSaveCopy, - const bool bLossless /*= false*/) +// Assumes ProjectFileIO::mFileName has been set to the desired path. +bool ProjectFileManager::DoSave(const FilePath & fileName, const bool fromSaveAs) { // See explanation above // ProjectDisabler disabler(this); auto &proj = mProject; - const auto &fileName = proj.GetFileName(); auto &window = GetProjectFrame( proj ); - auto &dirManager = DirManager::Get( proj ); auto &projectFileIO = ProjectFileIO::Get( proj ); const auto &settings = ProjectSettings::Get( proj ); - wxASSERT_MSG(!bWantSaveCopy || fromSaveAs, "Copy Project SHOULD only be available from SaveAs"); + wxASSERT_MSG(fromSaveAs, "Copy Project SHOULD only be available from SaveAs"); // Some confirmation dialogs - if (!bWantSaveCopy) { auto &tracks = TrackList::Get( proj ); - if ( ! tracks.Any() ) + if (!tracks.Any()) { - if ( UndoManager::Get( proj ).UnsavedChanges() - && settings.EmptyCanBeDirty()) { + if (UndoManager::Get( proj ).UnsavedChanges() && + settings.EmptyCanBeDirty()) + { int result = AudacityMessageBox( XO( -"Your project is now empty.\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nClick 'No', Edit > Undo until all tracks\nare open, then File > Save Project.\n\nSave anyway?"), + "Your project is now empty.\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nClick 'No', Edit > Undo until all tracks\nare open, then File > Save Project.\n\nSave anyway?"), XO("Warning - Empty Project"), wxYES_NO | wxICON_QUESTION, &window); if (result == wxNO) + { return false; + } } } - - // If the user has recently imported dependencies, show - // a dialog where the user can see audio files that are - // aliased by this project. The user may make the project - // self-contained during this dialog, it modifies the project! - if (mImportedDependencies) - { - bool bSuccess = ShowDependencyDialogIfNeeded(&proj, true); - if (!bSuccess) - return false; - mImportedDependencies = false; // do not show again - } } // End of confirmations - // // Always save a backup of the original project file - // - wxString safetyFileName; - if (wxFileExists(fileName)) { - + if (wxFileExists(fileName)) + { #ifdef __WXGTK__ safetyFileName = fileName + wxT("~"); #else safetyFileName = fileName + wxT(".bak"); #endif - bool bOK=true; if (wxFileExists(safetyFileName)) - bOK = wxRemoveFile(safetyFileName); + { + wxRemoveFile(safetyFileName); + } - if ( !wxRenameFile(fileName, safetyFileName) ) { + if ( !wxRenameFile(fileName, safetyFileName) ) + { AudacityMessageBox( XO( "Audacity failed to write file %s.\nPerhaps disk is full or not writable.") - .Format( safetyFileName ), + .Format(safetyFileName), XO("Error Writing to File"), wxICON_STOP, &window); @@ -320,332 +282,63 @@ bool ProjectFileManager::DoSave (const bool fromSaveAs, } } - bool success = true; - FilePath project, projName, projPath; - FilePaths strOtherNamesArray; - - auto cleanup = finally( [&] { - if (!safetyFileName.empty()) { - if (wxFileExists(fileName)) - wxRemove(fileName); - wxRename(safetyFileName, fileName); - } - - // strOtherNamesArray is a temporary array of file names, used only when - // saving compressed - if (!success) { - AudacityMessageBox( - XO( + bool success = projectFileIO.SaveProject(fileName); + if (!success) + { + AudacityMessageBox( + XO( "Could not save project. Perhaps %s \nis not writable or the disk is full.") - .Format( project ), - XO("Error Saving Project"), - wxICON_ERROR, - &window); + .Format(fileName), + XO("Error Saving Project"), + wxICON_ERROR, + &window); - // Make the export of tracks succeed all-or-none. - auto dir = project + wxT("_data"); - for ( auto &name : strOtherNamesArray ) - wxRemoveFile( dir + wxFileName::GetPathSeparator() + name); - // This has effect only if the folder is empty - wxFileName::Rmdir( dir ); - } - } ); - - if (fromSaveAs) { - // This block of code is duplicated in WriteXML, for now... - project = fileName; - wxFileName projFName{ fileName }; - if (projFName.GetExt() == wxT("aup")) - projFName.SetExt( {} ), project = projFName.GetFullPath(); - projName = wxFileNameFromPath(project) + wxT("_data"); - projPath = wxPathOnly(project); - - if( !wxDir::Exists( projPath ) ){ - AudacityMessageBox( - XO( -"Could not save project. Path not found. Try creating \ndirectory \"%s\" before saving project with this name.") - .Format( projPath ), - XO("Error Saving Project"), - wxICON_ERROR, - &window); - return (success = false); - } - - if (bWantSaveCopy) + if (wxFileExists(fileName)) { - // Do this before saving the .aup, because we accumulate - // strOtherNamesArray which affects the contents of the .aup - - // This populates the array strOtherNamesArray - success = this->SaveCopyWaveTracks( - project, bLossless, strOtherNamesArray); + wxRemoveFile(fileName); } + wxRename(safetyFileName, fileName); - if (!success) - return false; - } - - // Write the .aup now, before DirManager::SetProject, - // because it's easier to clean up the effects of successful write of .aup - // followed by failed SetProject, than the other way about. - // And that cleanup is done by the destructor of saveFile, if PostCommit() is - // not done. - // (SetProject, when it fails, cleans itself up.) - XMLFileWriter saveFile{ fileName, XO("Error Saving Project") }; - success = GuardedCall< bool >( [&] { - projectFileIO.WriteXMLHeader(saveFile); - projectFileIO.WriteXML(saveFile, bWantSaveCopy ? &strOtherNamesArray : nullptr); - // Flushes files, forcing space exhaustion errors before trying - // SetProject(): - saveFile.PreCommit(); - return true; - }, - MakeSimpleGuard(false), - // Suppress the usual error dialog for failed write, - // which is redundant here: - [](void*){} - ); - - if (!success) return false; - - { - std::vector> lockers; - Optional pSetter; - bool moving = true; - - if (fromSaveAs && !bWantSaveCopy) { - // We are about to move files from the current directory to - // the NEW directory. We need to make sure files that belonged - // to the last saved project don't get erased, so we "lock" them, so that - // ProjectSetter's constructor copies instead of moves the files. - // (Otherwise the NEW project would be fine, but the old one would - // be empty of all of its files.) - - if (mLastSavedTracks) { - moving = false; - lockers.reserve(mLastSavedTracks->size()); - for (auto wt : mLastSavedTracks->Any()) - lockers.push_back( - std::make_unique(wt)); - } - - // This renames the project directory, and moves or copies - // all of our block files over. - pSetter.emplace( dirManager, projPath, projName, true, moving ); - - if (!pSetter->Ok()){ - success = false; - return false; - } } - // Commit the writing of the .aup only now, after we know that the _data - // folder also saved with no problems. - // It is very unlikely that errors will happen: - // only renaming and removing of files, not writes that might exhaust space. - // So DO give a second dialog in case the unusual happens. - success = success && GuardedCall< bool >( [&] { - saveFile.PostCommit(); - return true; - } ); + UndoManager::Get(proj).StateSaved(); + ProjectStatus::Get(proj).Set(XO("Saved %s").Format(fileName)); - if (!success) - return false; - - // SAVE HAS SUCCEEDED -- following are further no-fail commit operations. - - if (pSetter) - pSetter->Commit(); - } - - if ( !bWantSaveCopy ) + if (mLastSavedTracks) { - // Now that we have saved the file, we can DELETE the auto-saved version - projectFileIO.DeleteCurrentAutoSaveFile(); + mLastSavedTracks->Clear(); + } + mLastSavedTracks = TrackList::Create(nullptr); - if ( projectFileIO.IsRecovered() ) - { - // This was a recovered file, that is, we have just overwritten the - // old, crashed .aup file. There may still be orphaned blockfiles in - // this directory left over from the crash, so we DELETE them now - dirManager.RemoveOrphanBlockfiles(); - - // Before we saved this, this was a recovered project, but now it is - // a regular project, so remember this. - projectFileIO.SetIsRecovered( false ); - projectFileIO.SetProjectTitle(); - } - else if (fromSaveAs) - { - // On save as, always remove orphaned blockfiles that may be left over - // because the user is trying to overwrite another project - dirManager.RemoveOrphanBlockfiles(); - } - - if (mLastSavedTracks) - mLastSavedTracks->Clear(); - mLastSavedTracks = TrackList::Create( nullptr ); - - auto &tracks = TrackList::Get( proj ); - for ( auto t : tracks.Any() ) { - mLastSavedTracks->Add(t->Duplicate()); - } - - UndoManager::Get( proj ).StateSaved(); + auto &tracks = TrackList::Get(proj); + for (auto t : tracks.Any()) + { + mLastSavedTracks->Add(t->Duplicate()); } // If we get here, saving the project was successful, so we can DELETE // the .bak file (because it now does not fit our block files anymore // anyway). - if (!safetyFileName.empty()) - wxRemoveFile(safetyFileName), - // cancel the cleanup: - safetyFileName = wxT(""); - - ProjectStatus::Get( proj ).Set( XO("Saved %s").Format( fileName ) ); + wxRemoveFile(safetyFileName); return true; } -bool ProjectFileManager::SaveCopyWaveTracks(const FilePath & strProjectPathName, - const bool bLossless, FilePaths &strOtherNamesArray) -{ - auto &project = mProject; - auto &tracks = TrackList::Get( project ); - auto &trackFactory = TrackFactory::Get( project ); - - wxString extension, fileFormat; - bool haveVorbis = -#if defined(USE_LIBVORBIS) - true; -#else - false; -#endif - if (!bLossless && haveVorbis) { - extension = wxT("ogg"); - fileFormat = wxT("OGG"); - } else{ - extension = wxT("wav"); - fileFormat = wxT("WAV"); - - // LLL: Temporary hack until I can figure out how to add an "ExportPCMCommand" - // to create a 32-bit float WAV file. It tells the ExportPCM exporter - // to use float when exporting the next WAV file. - // - // This was done as part of the resolution for bug #2062. - // - // See: ExportPCM.cpp, LoadEncoding() - auto cleanup = finally([&] { - gPrefs->DeleteEntry(wxT("/FileFormats/ExportFormat_SF1_ForceFloat")); - gPrefs->Flush(); - }); - gPrefs->Write(wxT("/FileFormats/ExportFormat_SF1_ForceFloat"), true); - gPrefs->Flush(); - } - - // Some of this is similar to code in ExportMultipleDialog::ExportMultipleByTrack - // but that code is really tied into the dialogs. - - // Copy the tracks because we're going to do some state changes before exporting. - unsigned int numWaveTracks = 0; - - auto ppSavedTrackList = TrackList::Create( nullptr ); - auto &pSavedTrackList = *ppSavedTrackList; - - auto trackRange = tracks.Any< WaveTrack >(); - for (auto pWaveTrack : trackRange) - { - numWaveTracks++; - pSavedTrackList.Add( trackFactory.DuplicateWaveTrack( *pWaveTrack ) ); - } - auto cleanup = finally( [&] { - // Restore the saved track states and clean up. - auto savedTrackRange = pSavedTrackList.Any(); - auto ppSavedTrack = savedTrackRange.begin(); - for (auto ppTrack = trackRange.begin(); - - *ppTrack && *ppSavedTrack; - - ++ppTrack, ++ppSavedTrack) - { - auto pWaveTrack = *ppTrack; - auto pSavedWaveTrack = *ppSavedTrack; - pWaveTrack->SetSelected(pSavedWaveTrack->GetSelected()); - pWaveTrack->SetMute(pSavedWaveTrack->GetMute()); - pWaveTrack->SetSolo(pSavedWaveTrack->GetSolo()); - - pWaveTrack->SetGain(pSavedWaveTrack->GetGain()); - pWaveTrack->SetPan(pSavedWaveTrack->GetPan()); - } - } ); - - if (numWaveTracks == 0) - // Nothing to save compressed => success. Delete the copies and go. - return true; - - // Okay, now some bold state-faking to default values. - for (auto pWaveTrack : trackRange) - { - pWaveTrack->SetSelected(false); - pWaveTrack->SetMute(false); - pWaveTrack->SetSolo(false); - - pWaveTrack->SetGain(1.0); - pWaveTrack->SetPan(0.0); - } - - FilePath strDataDirPathName = strProjectPathName + wxT("_data"); - if (!wxFileName::DirExists(strDataDirPathName) && - !wxFileName::Mkdir(strDataDirPathName, 0777, wxPATH_MKDIR_FULL)) - return false; - strDataDirPathName += wxFileName::GetPathSeparator(); - - // Export all WaveTracks to OGG. - bool bSuccess = true; - - Exporter theExporter{ project }; - wxFileName uniqueTrackFileName; - for (auto pTrack : (trackRange + &Track::IsLeader)) - { - SelectionStateChanger changer{ SelectionState::Get( project ), tracks }; - auto channels = TrackList::Channels(pTrack); - - for (auto channel : channels) - channel->SetSelected(true); - uniqueTrackFileName = wxFileName(strDataDirPathName, pTrack->GetName(), extension); - FileNames::MakeNameUnique(strOtherNamesArray, uniqueTrackFileName); - const auto startTime = channels.min( &Track::GetStartTime ); - const auto endTime = channels.max( &Track::GetEndTime ); - bSuccess = - theExporter.Process(channels.size(), - fileFormat, uniqueTrackFileName.GetFullPath(), true, - startTime, endTime); - - if (!bSuccess) - // If only some exports succeed, the cleanup is not done here - // but trusted to the caller - break; - } - - return bSuccess; -} - -bool ProjectFileManager::SaveAs(const wxString & newFileName, bool bWantSaveCopy /*= false*/, bool addToHistory /*= true*/) +bool ProjectFileManager::SaveAs(const wxString & newFileName, bool addToHistory /*= true*/) { auto &project = mProject; auto &projectFileIO = ProjectFileIO::Get( project ); - bool bLoadedFromAup = projectFileIO.IsLoadedFromAup(); // This version of SaveAs is invoked only from scripting and does not // prompt for a file name - auto oldFileName = project.GetFileName(); + auto oldFileName = projectFileIO.GetFileName(); - bool bOwnsNewAupName = bLoadedFromAup && (oldFileName == newFileName); + bool bOwnsNewName = !projectFileIO.IsTemporary() && (oldFileName == newFileName); //check to see if the NEW project file already exists. //We should only overwrite it if this project already has the same name, where the user //simply chose to use the save as command although the save command would have the effect. - if( !bOwnsNewAupName && wxFileExists(newFileName)) { + if( !bOwnsNewName && wxFileExists(newFileName)) { AudacityMessageDialog m( nullptr, XO("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."), @@ -655,95 +348,46 @@ bool ProjectFileManager::SaveAs(const wxString & newFileName, bool bWantSaveCopy return false; } - project.SetFileName( newFileName ); - bool success = false; - auto cleanup = finally( [&] { - if (!success || bWantSaveCopy) - // Restore file name on error - project.SetFileName( oldFileName ); - } ); - - //Don't change the title, unless we succeed. - //SetProjectTitle(); - - success = DoSave(!bOwnsNewAupName || bWantSaveCopy, bWantSaveCopy); - + auto success = DoSave(newFileName, !bOwnsNewName); if (success && addToHistory) { - FileHistory::Global().Append( project.GetFileName() ); - } - if (!success || bWantSaveCopy) // bWantSaveCopy doesn't actually change current project. - { - } - else { - projectFileIO.SetLoadedFromAup( true ); - projectFileIO.SetProjectTitle(); + FileHistory::Global().Append( projectFileIO.GetFileName() ); } return(success); } - -bool ProjectFileManager::SaveAs(bool bWantSaveCopy /*= false*/, bool bLossless /*= false*/) +bool ProjectFileManager::SaveAs() { auto &project = mProject; auto &projectFileIO = ProjectFileIO::Get( project ); auto &window = GetProjectFrame( project ); TitleRestorer Restorer( window, project ); // RAII bool bHasPath = true; - wxFileName filename{ project.GetFileName() }; - // Save a copy of the project with 32-bit float tracks. - if (bLossless) - bWantSaveCopy = true; + wxFileName filename{ projectFileIO.GetFileName() }; - bool bLoadedFromAup = projectFileIO.IsLoadedFromAup(); + wxString name = project.GetProjectName(); + if (!name.empty()) { + filename.SetName(name); + } // Bug 1304: Set a default file path if none was given. For Save/SaveAs if( !FileNames::IsPathAvailable( filename.GetPath( wxPATH_GET_VOLUME| wxPATH_GET_SEPARATOR) ) ){ bHasPath = false; - filename = FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path")); + filename.SetPath(FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path")).GetPath()); } - TranslatableString title; - TranslatableString message; - if (bWantSaveCopy) - { - if (bLossless) - { - title = XO("%sSave Lossless Copy of Project \"%s\" As...") - .Format( Restorer.sProjNumber,Restorer.sProjName ); - message = XO("\ -'Save Lossless Copy of Project' is for an Audacity project, not an audio file.\n\ -For an audio file that will open in other apps, use 'Export'.\n\n\ -\ -Lossless copies of project are a good way to backup your project, \n\ -with no loss of quality, but the projects are large.\n"); - } - else - { - title = XO("%sSave Compressed Copy of Project \"%s\" As...") - .Format( Restorer.sProjNumber, Restorer.sProjName ); - message = XO("\ -'Save Compressed Copy of Project' is for an Audacity project, not an audio file.\n\ -For an audio file that will open in other apps, use 'Export'.\n\n\ -\ -Compressed project files are a good way to transmit your project online, \n\ -but they have some loss of fidelity.\n"); - } - } - else - { - title = XO("%sSave Project \"%s\" As...") - .Format( Restorer.sProjNumber, Restorer.sProjName ); - message = XO("\ + TranslatableString title = XO("%sSave Project \"%s\" As...") + .Format( Restorer.sProjNumber, Restorer.sProjName ); + TranslatableString message = XO("\ 'Save Project' is for an Audacity project, not an audio file.\n\ For an audio file that will open in other apps, use 'Export'.\n"); - } + if (ShowWarningDialog(&window, wxT("FirstProjectSave"), message, true) != wxID_OK) { return false; } - bool bPrompt = (project.mBatchMode == 0) || (project.GetFileName().empty()); + bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty()); wxString fName; if (bPrompt) { @@ -755,7 +399,7 @@ For an audio file that will open in other apps, use 'Export'.\n"); title, filename.GetPath(), filename.GetFullName(), - wxT("aup"), + wxT("aup3"), { FileNames::AudacityProjects }, wxFD_SAVE | wxRESIZE_BORDER, &window); @@ -766,10 +410,10 @@ For an audio file that will open in other apps, use 'Export'.\n"); filename = fName; }; - filename.SetExt(wxT("aup")); + filename.SetExt(wxT("aup3")); fName = filename.GetFullPath(); - if ((bWantSaveCopy||!bPrompt) && filename.FileExists()) { + if (!bPrompt && filename.FileExists()) { // Saving a copy of the project should never overwrite an existing project. AudacityMessageDialog m( nullptr, @@ -780,20 +424,20 @@ For an audio file that will open in other apps, use 'Export'.\n"); return false; } - bool bOwnsNewAupName = bLoadedFromAup && ( project.GetFileName() == fName ); + bool bOwnsNewName = !projectFileIO.IsTemporary() && ( projectFileIO.GetFileName() == fName ); // Check to see if the project file already exists, and if it does // check that the project file 'belongs' to this project. // otherwise, prompt the user before overwriting. - if (!bOwnsNewAupName && filename.FileExists()) { + if (!bOwnsNewName && filename.FileExists()) { // Ensure that project of same name is not open in another window. // fName is the destination file. // mFileName is this project. // It is possible for mFileName == fName even when this project is not // saved to disk, and we then need to check the destination file is not // open in another window. - int mayOverwrite = ( project.GetFileName() == fName ) ? 2 : 1; + int mayOverwrite = ( projectFileIO.GetFileName() == fName ) ? 2 : 1; for ( auto p : AllProjects{} ) { - const wxFileName openProjectName{ p->GetFileName() }; + const wxFileName openProjectName{ ProjectFileIO::Get(*p).GetFileName() }; if (openProjectName.SameAs(fName)) { mayOverwrite -= 1; if (mayOverwrite == 0) @@ -833,33 +477,15 @@ will be irreversibly overwritten.").Format( fName, fName ); } } - auto oldFileName = project.GetFileName(); - project.SetFileName( fName ); - bool success = false; - auto cleanup = finally( [&] { - if (!success || bWantSaveCopy) - // Restore file name on error - project.SetFileName( oldFileName ); - } ); - - success = DoSave(!bOwnsNewAupName || bWantSaveCopy, bWantSaveCopy, bLossless); - + auto success = DoSave(fName, !bOwnsNewName); if (success) { - FileHistory::Global().Append( project.GetFileName() ); + FileHistory::Global().Append( projectFileIO.GetFileName() ); if( !bHasPath ) { gPrefs->Write( wxT("/SaveAs/Path"), filename.GetPath()); gPrefs->Flush(); } } - if (!success || bWantSaveCopy) // bWantSaveCopy doesn't actually change current project. - { - } - else { - projectFileIO.SetLoadedFromAup( true ); - projectFileIO.SetProjectTitle(); - } - return(success); } @@ -889,8 +515,8 @@ bool ProjectFileManager::SaveFromTimerRecording(wxFileName fnFile) // MY: To allow SaveAs from Timer Recording we need to check what // the value of mFileName is before we change it. FilePath sOldFilename; - if (projectFileIO.IsProjectSaved()) { - sOldFilename = project.GetFileName(); + if (!projectFileIO.IsModified()) { + sOldFilename = projectFileIO.GetFileName(); } // MY: If the project file already exists then bail out @@ -900,23 +526,13 @@ bool ProjectFileManager::SaveFromTimerRecording(wxFileName fnFile) return false; } - project.SetFileName( sNewFileName ); - bool bSuccess = false; - auto cleanup = finally( [&] { - if (!bSuccess) - // Restore file name on error - project.SetFileName( sOldFilename ); - } ); + auto success = DoSave(sNewFileName, true); - bSuccess = DoSave(true, false); - - if (bSuccess) { - FileHistory::Global().Append( project.GetFileName() ); - projectFileIO.SetLoadedFromAup( true ); - projectFileIO.SetProjectTitle(); + if (success) { + FileHistory::Global().Append( projectFileIO.GetFileName() ); } - return bSuccess; + return success; } void ProjectFileManager::CloseLock() @@ -925,9 +541,12 @@ void ProjectFileManager::CloseLock() // the blockfiles aren't deleted on disk when we DELETE the blockfiles // in memory. After it's locked, DELETE the data structure so that // there's no memory leak. - if (mLastSavedTracks) { + if (mLastSavedTracks) + { for (auto wt : mLastSavedTracks->Any()) + { wt->CloseLock(); + } mLastSavedTracks->Clear(); mLastSavedTracks.reset(); @@ -979,7 +598,7 @@ bool ProjectFileManager::IsAlreadyOpen(const FilePath &projPathName) auto start = AllProjects{}.begin(), finish = AllProjects{}.end(), iter = std::find_if( start, finish, [&]( const AllProjects::value_type &ptr ){ - return newProjPathName.SameAs(wxFileNameWrapper{ ptr->GetFileName() }); + return newProjPathName.SameAs(wxFileNameWrapper{ ProjectFileIO::Get(*ptr).GetFileName() }); } ); if (iter != finish) { auto errMsg = @@ -995,21 +614,6 @@ bool ProjectFileManager::IsAlreadyOpen(const FilePath &projPathName) return false; } -XMLTagHandler * -ProjectFileManager::RecordingRecoveryFactory( AudacityProject &project ) { - auto &ProjectFileManager = Get( project ); - auto &ptr = ProjectFileManager.mRecordingRecoveryHandler; - if (!ptr) - ptr = - std::make_unique( &project ); - return ptr.get(); -} - -ProjectFileIORegistry::Entry -ProjectFileManager::sRecoveryFactory{ - wxT("recordingrecovery"), RecordingRecoveryFactory -}; - // XML handler for tag class ImportXMLTagHandler final : public XMLTagHandler { @@ -1033,19 +637,6 @@ bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs) if (wxStrcmp(tag, wxT("import")) || attrs==NULL || (*attrs)==NULL || wxStrcmp(*attrs++, wxT("filename"))) return false; wxString strAttr = *attrs; - if (!XMLValueChecker::IsGoodPathName(strAttr)) - { - // Maybe strAttr is just a fileName, not the full path. Try the project data directory. - wxFileNameWrapper fileName{ - DirManager::Get( *mProject ).GetProjectDataDir(), strAttr }; - if (XMLValueChecker::IsGoodFileName(strAttr, fileName.GetPath(wxPATH_GET_VOLUME))) - strAttr = fileName.GetFullPath(); - else - { - wxLogWarning(wxT("Could not import file: %s"), strAttr); - return false; - } - } WaveTrackArray trackArray; @@ -1089,21 +680,6 @@ bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs) return bSuccess; }; -XMLTagHandler * -ProjectFileManager::ImportHandlerFactory( AudacityProject &project ) { - auto &ProjectFileManager = Get( project ); - auto &ptr = ProjectFileManager.mImportXMLTagHandler; - if (!ptr) - ptr = - std::make_unique( &project ); - return ptr.get(); -} - -ProjectFileIORegistry::Entry -ProjectFileManager::sImportHandlerFactory{ - wxT("import"), ImportHandlerFactory -}; - // FIXME:? TRAP_ERR This should return a result that is checked. // See comment in AudacityApp::MRUOpen(). void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory) @@ -1113,7 +689,6 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory auto &projectFileIO = ProjectFileIO::Get( project ); auto &tracks = TrackList::Get( project ); auto &trackPanel = TrackPanel::Get( project ); - auto &dirManager = DirManager::Get( project ); auto &window = ProjectWindow::Get( project ); // On Win32, we may be given a short (DOS-compatible) file name on rare @@ -1132,7 +707,6 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory if (IsAlreadyOpen(fileName)) return; - // Data loss may occur if users mistakenly try to open ".aup.bak" files // left over from an unsuccessful save or by previous versions of Audacity. // So we always refuse to open such files. @@ -1156,14 +730,17 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, 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: - - char buf[16]; { wxFFile ff(fileName, wxT("rb")); + + auto cleanup = finally([&] + { + if (ff.IsOpened()) + { + ff.Close(); + } + }); + if (!ff.IsOpened()) { AudacityMessageBox( XO("Could not open file: %s").Format( fileName ), @@ -1172,76 +749,45 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory &window); return; } - int numRead = ff.Read(buf, 15); - if (numRead != 15) { + + char buf[7]; + auto numRead = ff.Read(buf, 6); + if (numRead != 6) { AudacityMessageBox( XO("File may be invalid or corrupted: \n%s").Format( fileName ), XO("Error Opening File or Project"), wxOK | wxCENTRE, &window); - ff.Close(); return; } - buf[15] = 0; - } - - wxString temp = LAT1CTOWX(buf); - - if (temp == wxT("AudacityProject")) { - // It's an Audacity 1.0 (or earlier) project file. - // If they bail out, return and do no more. - if( !projectFileIO.WarnOfLegacyFile() ) - return; - // Convert to the NEW format. - bool success = ConvertLegacyProjectFile(wxFileName{ fileName }); - if (!success) { - AudacityMessageBox( - XO( -"Audacity was unable to convert an Audacity 1.0 project to the new project format."), - XO("Error Opening Project"), - wxOK | wxCENTRE, - &window); - return; - } - else { - temp = wxT(" tag, so save it as a normal project, - // with no tags. - Save(); +#endif } } else { @@ -1379,9 +915,6 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory tracks.Clear(); //tracks.Clear(true); - project.SetFileName( wxT("") ); - projectFileIO.SetProjectTitle(); - wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr.Debug()); ShowErrorDialog( @@ -1394,7 +927,7 @@ void ProjectFileManager::OpenFile(const FilePath &fileNameArg, bool addtohistory std::vector< std::shared_ptr< Track > > ProjectFileManager::AddImportedTracks(const FilePath &fileName, - TrackHolders &&newTracks) + TrackHolders &&newTracks) { auto &project = mProject; auto &history = ProjectHistory::Get( project ); @@ -1405,9 +938,11 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName, SelectUtilities::SelectNone( project ); + wxFileName fn(fileName); + bool initiallyEmpty = tracks.empty(); double newRate = 0; - wxString trackNameBase = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.'); + wxString trackNameBase = fn.GetName(); int i = -1; // Must add all tracks first (before using Track::IsLeader) @@ -1450,18 +985,6 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName, newTrack->TypeSwitch( [&](WaveTrack *wt) { if (newRate == 0) newRate = wt->GetRate(); - - // Check if NEW track contains aliased blockfiles and if yes, - // remember this to show a warning later - if(WaveClip* clip = wt->GetClipByIndex(0)) { - BlockArray &blocks = clip->GetSequence()->GetBlockArray(); - if (blocks.size()) - { - SeqBlock& block = blocks[0]; - if (block.f->IsAlias()) - SetImportedDependencies( true ); - } - } }); } @@ -1485,11 +1008,10 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName, wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT); #endif - if (initiallyEmpty && !projectFileIO.IsProjectSaved() ) { - wxString name = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast(wxT('.')); - project.SetFileName( - ::wxPathOnly(fileName) + wxFILE_SEP_PATH + name + wxT(".aup") ); - projectFileIO.SetLoadedFromAup( false ); + // If the project was clean and temporary (not permanently saved), then set + // the filename to the just imported path. + if (initiallyEmpty && projectFileIO.IsTemporary()) { + project.SetProjectName(fn.GetName()); projectFileIO.SetProjectTitle(); } @@ -1504,7 +1026,6 @@ bool ProjectFileManager::Import( const FilePath &fileName, WaveTrackArray* pTrackArray /*= NULL*/) { auto &project = mProject; - auto &dirManager = DirManager::Get( project ); auto oldTags = Tags::Get( project ).shared_from_this(); TrackHolders newTracks; TranslatableString errorMessage; diff --git a/src/ProjectFileManager.h b/src/ProjectFileManager.h index 8d53e83c3..12ccb5baf 100644 --- a/src/ProjectFileManager.h +++ b/src/ProjectFileManager.h @@ -20,8 +20,6 @@ Paul Licameli split from AudacityProject.h class wxString; class wxFileName; class AudacityProject; -class ImportXMLTagHandler; -class RecordingRecoveryHandler; class Track; class TrackList; class WaveTrack; @@ -48,7 +46,7 @@ public: bool decodeError; bool parseSuccess; bool trackError; - TranslatableString errorString; + const TranslatableString errorString; wxString helpUrl; }; ReadProjectResults ReadProjectFile( const FilePath &fileName ); @@ -58,9 +56,8 @@ public: void CloseLock(); bool Save(); - bool SaveAs(bool bWantSaveCopy = false, bool bLossless = false); - bool SaveAs(const wxString & newFileName, bool bWantSaveCopy = false, - bool addToHistory = true); + bool SaveAs(); + bool SaveAs(const wxString & newFileName, bool addToHistory = true); // strProjectPathName is full path for aup except extension bool SaveFromTimerRecording( wxFileName fnFile ); @@ -104,32 +101,13 @@ public: bool GetMenuClose() const { return mMenuClose; } void SetMenuClose(bool value) { mMenuClose = value; } -private: - void SetImportedDependencies( bool value ) { mImportedDependencies = value; } - - // Push names of NEW export files onto the path list - bool SaveCopyWaveTracks(const FilePath & strProjectPathName, - bool bLossless, FilePaths &strOtherNamesArray); - bool DoSave(bool fromSaveAs, bool bWantSaveCopy, bool bLossless = false); - - // Declared in this class so that they can have access to private members - static XMLTagHandler *RecordingRecoveryFactory( AudacityProject &project ); - static ProjectFileIORegistry::Entry sRecoveryFactory; - static XMLTagHandler *ImportHandlerFactory( AudacityProject &project ); - static ProjectFileIORegistry::Entry sImportHandlerFactory; +private: + bool DoSave(const FilePath & fileName, bool fromSaveAs); AudacityProject &mProject; std::shared_ptr mLastSavedTracks; - // The handler that handles recovery of tags - std::unique_ptr mRecordingRecoveryHandler; - - std::unique_ptr mImportXMLTagHandler; - - // Dependencies have been imported and a warning should be shown on save - bool mImportedDependencies{ false }; - // Are we currently closing as the result of a menu command? bool mMenuClose{ false }; }; diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index 45eb8803a..7f67feb51 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -15,12 +15,9 @@ Paul Licameli split from AudacityProject.cpp #include "AdornedRulerPanel.h" #include "AudioIO.h" #include "AutoRecovery.h" -#include "BlockFile.h" #include "Clipboard.h" -#include "DirManager.h" #include "FileNames.h" #include "Menus.h" -#include "MissingAliasFileDialog.h" #include "ModuleManager.h" #include "Project.h" #include "ProjectAudioIO.h" @@ -541,7 +538,6 @@ AudacityProject *ProjectManager::New() ProjectFileIO::Get( *p ).SetProjectTitle(); - MissingAliasFilesDialog::SetShouldShow(true); MenuManager::Get( project ).CreateMenusAndCommands( project ); projectHistory.InitialState(); @@ -593,10 +589,6 @@ AudacityProject *ProjectManager::New() return p; } -// LL: All objects that have a reference to the DirManager should -// be deleted before the final mDirManager->Deref() in this -// routine. Failing to do so can cause unwanted recursion -// and/or attempts to DELETE objects twice. void ProjectManager::OnCloseWindow(wxCloseEvent & event) { auto &project = mProject; @@ -697,7 +689,7 @@ void ProjectManager::OnCloseWindow(wxCloseEvent & event) // The project is now either saved or the user doesn't want to save it, // so there's no need to keep auto save info around anymore - projectFileIO.DeleteCurrentAutoSaveFile(); + projectFileIO.AutoSaveDelete(); // DMM: Save the size of the last window the user closes // @@ -756,15 +748,6 @@ void ProjectManager::OnCloseWindow(wxCloseEvent & event) // references to the DirManager. UndoManager::Get( project ).ClearStates(); - // MM: Tell the DirManager it can now DELETE itself - // if it finds it is no longer needed. If it is still - // used (f.e. by the clipboard), it will recognize this - // and will destroy itself later. - // - // LL: All objects with references to the DirManager should - // have been deleted before this. - DirManager::Destroy( project ); - // Remove self from the global array, but defer destruction of self auto pSelf = AllProjects{}.Remove( project ); wxASSERT( pSelf ); @@ -914,7 +897,6 @@ void ProjectManager::ResetProjectToEmpty() { TrackUtilities::DoRemoveTracks( project ); // A new DirManager. - DirManager::Reset( project ); TrackFactory::Reset( project ); projectFileManager.Reset(); @@ -936,7 +918,6 @@ void ProjectManager::OnTimer(wxTimerEvent& WXUNUSED(event)) { auto &project = mProject; auto &projectAudioIO = ProjectAudioIO::Get( project ); - auto &dirManager = DirManager::Get( project ); auto mixerToolBar = &MixerToolBar::Get( project ); mixerToolBar->UpdateControls(); @@ -944,7 +925,7 @@ void ProjectManager::OnTimer(wxTimerEvent& WXUNUSED(event)) // gAudioIO->GetNumCaptureChannels() should only be positive // when we are recording. if (projectAudioIO.GetAudioIOToken() > 0 && gAudioIO->GetNumCaptureChannels() > 0) { - wxLongLong freeSpace = dirManager.GetFreeDiskSpace(); + wxLongLong freeSpace = ProjectFileIO::Get(project).GetFreeDiskSpace(); if (freeSpace >= 0) { int iRecordingMins = GetEstimatedRecordingMinsLeftOnDisk(gAudioIO->GetNumCaptureChannels()); @@ -1030,7 +1011,7 @@ int ProjectManager::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels) { } // Find out how much free space we have on disk - wxLongLong lFreeSpace = DirManager::Get( project ).GetFreeDiskSpace(); + wxLongLong lFreeSpace = ProjectFileIO::Get( project ).GetFreeDiskSpace(); if (lFreeSpace < 0) { return 0; } diff --git a/src/SampleBlock.cpp b/src/SampleBlock.cpp new file mode 100644 index 000000000..5d42047d9 --- /dev/null +++ b/src/SampleBlock.cpp @@ -0,0 +1,730 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +SampleBlock.cpp + +**********************************************************************/ + +#include "Audacity.h" +#include "SampleBlock.h" + +#include + +#include + +#include "ProjectFileIO.h" +#include "SampleFormat.h" +#include "xml/XMLWriter.h" + +// static +SampleBlockPtr SampleBlock::Create(AudacityProject *project, + samplePtr src, + size_t numsamples, + sampleFormat srcformat) +{ + auto sb = std::make_shared(project); + + if (sb) + { + if (sb->SetSamples(src, numsamples, srcformat)) + { + return sb; + } + } + + return nullptr; +} + +// static +SampleBlockPtr SampleBlock::CreateSilent(AudacityProject *project, + size_t numsamples, + sampleFormat srcformat) +{ + auto sb = std::make_shared(project); + + if (sb) + { + if (sb->SetSilent(numsamples, srcformat)) + { + return sb; + } + } + + return nullptr; +} + + +// static +SampleBlockPtr SampleBlock::CreateFromXML(AudacityProject *project, + sampleFormat srcformat, + const wxChar **attrs) +{ + auto sb = std::make_shared(project); + sb->mSampleFormat = srcformat; + + int found = 0; + + // loop through attrs, which is a null-terminated list of attribute-value pairs + while(*attrs) + { + const wxChar *attr = *attrs++; + const wxChar *value = *attrs++; + + if (!value) + { + break; + } + + const wxString strValue = value; // promote string, we need this for all + double dblValue; + long long nValue; + + if (XMLValueChecker::IsGoodInt(strValue) && strValue.ToLongLong(&nValue) && (nValue >= 0)) + { + if (wxStrcmp(attr, wxT("blockid")) == 0) + { + if (!sb->Load((SampleBlockID) nValue)) + { + return nullptr; + } + found++; + } + else if (wxStrcmp(attr, wxT("samplecount")) == 0) + { + sb->mSampleCount = nValue; + sb->mSampleBytes = sb->mSampleCount * SAMPLE_SIZE(sb->mSampleFormat); + found++; + } + } + else if (XMLValueChecker::IsGoodString(strValue) && Internat::CompatibleToDouble(strValue, &dblValue)) + { + if (wxStricmp(attr, wxT("min")) == 0) + { + sb->mSumMin = dblValue; + found++; + } + else if (wxStricmp(attr, wxT("max")) == 0) + { + sb->mSumMax = dblValue; + found++; + } + else if ((wxStricmp(attr, wxT("rms")) == 0) && (dblValue >= 0.0)) + { + sb->mSumRms = dblValue; + found++; + } + } + } + + // Were all attributes found? + if (found != 5) + { + return nullptr; + } + + return sb; +} + +// static +SampleBlockPtr SampleBlock::Get(AudacityProject *project, + SampleBlockID sbid) +{ + auto sb = std::make_shared(project); + + if (sb) + { + if (!sb->Load(sbid)) + { + return nullptr; + } + } + + return sb; +} + +SampleBlock::SampleBlock(AudacityProject *project) +: mProject(project), + mIO(ProjectFileIO::Get(*project)) +{ + mValid = false; + mSilent = false; + mRefCnt = 0; + + mBlockID = 0; + + mSampleFormat = floatSample; + mSampleBytes = 0; + mSampleCount = 0; + + mSummary256Bytes = 0; + mSummary64kBytes = 0; + mSumMin = 0.0; + mSumMax = 0.0; + mSumRms = 0.0; +} + +SampleBlock::~SampleBlock() +{ + if (mRefCnt == 0) + { + Delete(); + } +} + +void SampleBlock::Lock() +{ + ++mRefCnt; +} + +void SampleBlock::Unlock() +{ + --mRefCnt; +} + +void SampleBlock::CloseLock() +{ + Lock(); +} + +SampleBlockID SampleBlock::GetBlockID() +{ + return mBlockID; +} + +sampleFormat SampleBlock::GetSampleFormat() const +{ + return mSampleFormat; +} + +size_t SampleBlock::GetSampleCount() const +{ + return mSampleCount; +} + +size_t SampleBlock::GetSamples(samplePtr dest, + sampleFormat destformat, + size_t sampleoffset, + size_t numsamples) +{ + return GetBlob(dest, + destformat, + "samples", + mSampleFormat, + sampleoffset * SAMPLE_SIZE(mSampleFormat), + numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat); +} + +bool SampleBlock::SetSamples(samplePtr src, + size_t numsamples, + sampleFormat srcformat) +{ + mSampleFormat = srcformat; + + mSampleCount = numsamples; + mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat); + mSamples.reinit(mSampleBytes); + memcpy(mSamples.get(), src, mSampleBytes); + + CalcSummary(); + + return Commit(); +} + +bool SampleBlock::SetSilent(size_t numsamples, sampleFormat srcformat) +{ + mSampleFormat = srcformat; + + mSampleCount = numsamples; + mSampleBytes = mSampleCount * SAMPLE_SIZE(mSampleFormat); + mSamples.reinit(mSampleBytes); + memset(mSamples.get(), 0, mSampleBytes); + + CalcSummary(); + + mSilent = true; + + return Commit(); +} + +bool SampleBlock::GetSummary256(float *dest, + size_t frameoffset, + size_t numframes) +{ + return GetSummary(dest, frameoffset, numframes, "summary256", mSummary256Bytes); +} + +bool SampleBlock::GetSummary64k(float *dest, + size_t frameoffset, + size_t numframes) +{ + return GetSummary(dest, frameoffset, numframes, "summary64k", mSummary64kBytes); +} + +bool SampleBlock::GetSummary(float *dest, + size_t frameoffset, + size_t numframes, + const char *srccolumn, + size_t srcbytes) +{ + return GetBlob(dest, + floatSample, + srccolumn, + floatSample, + frameoffset * 3 * SAMPLE_SIZE(floatSample), + numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample); +} + +double SampleBlock::GetSumMin() const +{ + return mSumMin; +} + +double SampleBlock::GetSumMax() const +{ + return mSumMax; +} + +double SampleBlock::GetSumRms() const +{ + return mSumRms; +} + +/// Retrieves the minimum, maximum, and maximum RMS of the +/// specified sample data in this block. +/// +/// @param start The offset in this block where the region should begin +/// @param len The number of samples to include in the region +MinMaxRMS SampleBlock::GetMinMaxRMS(size_t start, size_t len) const +{ + float min = FLT_MAX; + float max = -FLT_MAX; + float sumsq = 0; + + if (mValid && start < mSampleCount) + { + float *samples = &((float *) mSamples.get())[start]; + len = std::min(len, mSampleCount - start); + + for (int i = 0; i < len; ++i, ++samples) + { + float sample = *samples; + + if (sample > max) + { + max = sample; + } + + if (sample < min) + { + min = sample; + } + + sumsq += (sample * sample); + } + } + + return { min, max, (float) sqrt(sumsq / len) }; +} + +/// Retrieves the minimum, maximum, and maximum RMS of this entire +/// block. This is faster than the other GetMinMax function since +/// these values are already computed. +MinMaxRMS SampleBlock::GetMinMaxRMS() const +{ + return { (float) mSumMin, (float) mSumMax, (float) mSumRms }; +} + +size_t SampleBlock::GetSpaceUsage() const +{ + return mSampleCount * SAMPLE_SIZE(mSampleFormat); +} + +size_t SampleBlock::GetBlob(void *dest, + sampleFormat destformat, + const char *srccolumn, + sampleFormat srcformat, + size_t srcoffset, + size_t srcbytes) +{ + wxASSERT(mBlockID > 0); + + if (!mValid && mBlockID) + { + Load(mBlockID); + } + + int rc; + size_t minbytes = 0; + + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "SELECT %s FROM sampleblocks WHERE blockid = %d;", + srccolumn, + mBlockID); + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(mIO.DB(), sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + } + else + { + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + } + else + { + samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0); + size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0); + + srcoffset = std::min(srcoffset, blobbytes); + minbytes = std::min(srcbytes, blobbytes - srcoffset); + + if (srcoffset != 0) + { + srcoffset += 0; + } + CopySamples(src + srcoffset, + srcformat, + (samplePtr) dest, + destformat, + minbytes / SAMPLE_SIZE(srcformat)); + + dest = ((samplePtr) dest) + minbytes; + } + } + + if (srcbytes - minbytes) + { + memset(dest, 0, srcbytes - minbytes); + } + + return srcbytes; +} + +bool SampleBlock::Load(SampleBlockID sbid) +{ + wxASSERT(sbid > 0); + + int rc; + + mValid = false; + mSummary256Bytes = 0; + mSummary64kBytes = 0; + mSampleCount = 0; + mSampleBytes = 0; + + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "SELECT sampleformat, summin, summax, sumrms," + " length('summary256'), length('summary64k'), length('samples')" + " FROM sampleblocks WHERE blockid = %d;", + sbid); + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(mIO.DB(), sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + // handle error + return false; + } + + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + // handle error + return false; + } + + mBlockID = sbid; + mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0); + mSumMin = sqlite3_column_double(stmt, 1); + mSumMax = sqlite3_column_double(stmt, 2); + mSumRms = sqlite3_column_double(stmt, 3); + mSummary256Bytes = sqlite3_column_int(stmt, 4); + mSummary64kBytes = sqlite3_column_int(stmt, 5); + mSampleBytes = sqlite3_column_int(stmt, 6); + mSampleCount = mSampleBytes / SAMPLE_SIZE(mSampleFormat); + + mValid = true; + + return true; +} + +bool SampleBlock::Commit() +{ + int rc; + + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "INSERT INTO sampleblocks (%s) VALUES(?,?,?,?,?,?,?);", + columns); + + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + rc = sqlite3_prepare_v2(mIO.DB(), sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + // handle error + return false; + } + + sqlite3_bind_int(stmt, 1, mSampleFormat); + sqlite3_bind_double(stmt, 2, mSumMin); + sqlite3_bind_double(stmt, 3, mSumMax); + sqlite3_bind_double(stmt, 4, mSumRms); + sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC); + sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC); + sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + // handle error + return false; + } + + mBlockID = sqlite3_last_insert_rowid(mIO.DB()); + + mSamples.reset(); + mSummary256.reset(); + mSummary64k.reset(); + + mValid = true; + + return true; +} + +void SampleBlock::Delete() +{ + if (mBlockID) + { + int rc; + + char sql[256]; + sqlite3_snprintf(sizeof(sql), + sql, + "DELETE FROM sampleblocks WHERE blockid = %lld;", + mBlockID); + + rc = sqlite3_exec(mIO.DB(), sql, nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) + { + wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(mIO.DB())); + // handle error + return; + } + } +} + +void SampleBlock::SaveXML(XMLWriter &xmlFile) +{ + xmlFile.WriteAttr(wxT("blockid"), mBlockID); + xmlFile.WriteAttr(wxT("samplecount"), mSampleCount); + xmlFile.WriteAttr(wxT("len256"), mSummary256Bytes); + xmlFile.WriteAttr(wxT("len64k"), mSummary64kBytes); + xmlFile.WriteAttr(wxT("min"), mSumMin); + xmlFile.WriteAttr(wxT("max"), mSumMax); + xmlFile.WriteAttr(wxT("rms"), mSumRms); +} + +/// Calculates summary block data describing this sample data. +/// +/// This method also has the side effect of setting the mSumMin, +/// mSumMax, and mSumRms members of this class. +/// +/// @param buffer A buffer containing the sample data to be analyzed +/// @param len The length of the sample data +/// @param format The format of the sample data. +void SampleBlock::CalcSummary() +{ + Floats samplebuffer; + float *samples; + + if (mSampleFormat == floatSample) + { + samples = (float *) mSamples.get(); + } + else + { + samplebuffer.reinit((unsigned) mSampleCount); + CopySamples(mSamples.get(), + mSampleFormat, + (samplePtr) samplebuffer.get(), + floatSample, + mSampleCount); + samples = samplebuffer.get(); + } + + int fields = 3; /* min, max, rms */ + int bytesPerFrame = fields * sizeof(float); + int frames64k = (mSampleCount + 65535) / 65536; + int frames256 = frames64k * 256; + + mSummary256Bytes = frames256 * bytesPerFrame; + mSummary64kBytes = frames64k * bytesPerFrame; + + mSummary256.reinit(mSummary256Bytes); + mSummary64k.reinit(mSummary64kBytes); + + float *summary256 = (float *) mSummary256.get(); + float *summary64k = (float *) mSummary64k.get(); + + float min; + float max; + float sumsq; + double totalSquares = 0.0; + double fraction = 0.0; + + // Recalc 256 summaries + int sumLen = (mSampleCount + 255) / 256; + int summaries = 256; + + for (int i = 0; i < sumLen; ++i) + { + min = samples[i * 256]; + max = samples[i * 256]; + sumsq = min * min; + + int jcount = 256; + if (jcount > mSampleCount - i * 256) + { + jcount = mSampleCount - i * 256; + fraction = 1.0 - (jcount / 256.0); + } + + for (int j = 1; j < jcount; ++j) + { + float f1 = samples[i * 256 + j]; + sumsq += f1 * f1; + + if (f1 < min) + { + min = f1; + } + else if (f1 > max) + { + max = f1; + } + } + + totalSquares += sumsq; + + summary256[i * 3] = min; + summary256[i * 3 + 1] = max; + // The rms is correct, but this may be for less than 256 samples in last loop. + summary256[i * 3 + 2] = (float) sqrt(sumsq / jcount); + } + + for (int i = sumLen; i < frames256; ++i) + { + // filling in the remaining bits with non-harming/contributing values + // rms values are not "non-harming", so keep count of them: + summaries--; + summary256[i * 3] = FLT_MAX; // min + summary256[i * 3 + 1] = -FLT_MAX; // max + summary256[i * 3 + 2] = 0.0f; // rms + } + + // Calculate now while we can do it accurately + mSumRms = sqrt(totalSquares / mSampleCount); + + // Recalc 64K summaries + sumLen = (mSampleCount + 65535) / 65536; + + for (int i = 0; i < sumLen; ++i) + { + min = summary256[3 * i * 256]; + max = summary256[3 * i * 256 + 1]; + sumsq = summary256[3 * i * 256 + 2]; + sumsq *= sumsq; + + for (int j = 1; j < 256; ++j) + { + // we can overflow the useful summary256 values here, but have put + // non-harmful values in them + if (summary256[3 * (i * 256 + j)] < min) + { + min = summary256[3 * (i * 256 + j)]; + } + + if (summary256[3 * (i * 256 + j) + 1] > max) + { + max = summary256[3 * (i * 256 + j) + 1]; + } + + float r1 = summary256[3 * (i * 256 + j) + 2]; + sumsq += r1 * r1; + } + + double denom = (i < sumLen - 1) ? 256.0 : summaries - fraction; + float rms = (float) sqrt(sumsq / denom); + + summary64k[i * 3] = min; + summary64k[i * 3 + 1] = max; + summary64k[i * 3 + 2] = rms; + } + + for (int i = sumLen; i < frames64k; ++i) + { + wxASSERT_MSG(false, wxT("Out of data for mSummaryInfo")); // Do we ever get here? + + summary64k[i * 3] = 0.0f; // probably should be FLT_MAX, need a test case + summary64k[i * 3 + 1] = 0.0f; // probably should be -FLT_MAX, need a test case + summary64k[i * 3 + 2] = 0.0f; // just padding + } + + // Recalc block-level summary (mRMS already calculated) + min = summary64k[0]; + max = summary64k[1]; + + for (int i = 1; i < sumLen; ++i) + { + if (summary64k[i * 3] < min) + { + min = summary64k[i * 3]; + } + + if (summary64k[i * 3 + 1] > max) + { + max = summary64k[i * 3 + 1]; + } + } + + mSumMin = min; + mSumMax = max; +} diff --git a/src/SampleBlock.h b/src/SampleBlock.h new file mode 100644 index 000000000..44c1d2b5b --- /dev/null +++ b/src/SampleBlock.h @@ -0,0 +1,137 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +SampleBlock.h + +**********************************************************************/ + +#ifndef __AUDACITY_SAMPLE_BLOCK__ +#define __AUDACITY_SAMPLE_BLOCK__ + +#include "ClientData.h" // to inherit + +#include + +class AudacityProject; +class ProjectFileIO; +class XMLWriter; + +class SampleBlock; +using SampleBlockPtr = std::shared_ptr; +using SampleBlockID = sqlite3_int64; + +class MinMaxRMS +{ +public: + float min; + float max; + float RMS; +}; + +class SampleBlock +{ +public: + SampleBlock(AudacityProject *project); + virtual ~SampleBlock(); + + static SampleBlockPtr Get(AudacityProject *project, + SampleBlockID sbid); + + static SampleBlockPtr Create(AudacityProject *project, + samplePtr src, + size_t numsamples, + sampleFormat srcformat); + + static SampleBlockPtr CreateSilent(AudacityProject *project, + size_t numsamples, + sampleFormat srcformat); + + static SampleBlockPtr CreateFromXML(AudacityProject *project, + sampleFormat srcformat, + const wxChar **attrs); + + void Lock(); + void Unlock(); + void CloseLock(); + + bool SetSamples(samplePtr src, size_t numsamples, sampleFormat srcformat); + + bool SetSilent(size_t numsamples, sampleFormat srcformat); + + bool Commit(); + + void Delete(); + + SampleBlockID GetBlockID(); + + size_t GetSamples(samplePtr dest, + sampleFormat destformat, + size_t sampleoffset, + size_t numsamples); + sampleFormat GetSampleFormat() const; + size_t GetSampleCount() const; + + bool GetSummary256(float *dest, size_t frameoffset, size_t numframes); + bool GetSummary64k(float *dest, size_t frameoffset, size_t numframes); + double GetSumMin() const; + double GetSumMax() const; + double GetSumRms() const; + + /// Gets extreme values for the specified region + MinMaxRMS GetMinMaxRMS(size_t start, size_t len) const; + + /// Gets extreme values for the entire block + MinMaxRMS GetMinMaxRMS() const; + + size_t GetSpaceUsage() const; + void SaveXML(XMLWriter &xmlFile); + +private: + bool Load(SampleBlockID sbid); + bool GetSummary(float *dest, + size_t frameoffset, + size_t numframes, + const char *srccolumn, + size_t srcbytes); + size_t GetBlob(void *dest, + sampleFormat destformat, + const char *srccolumn, + sampleFormat srcformat, + size_t srcoffset, + size_t srcbytes); + void CalcSummary(); + +private: + AudacityProject *mProject; + ProjectFileIO & mIO; + bool mValid; + bool mDirty; + bool mSilent; + int mRefCnt; + + SampleBlockID mBlockID; + + ArrayOf mSamples; + size_t mSampleBytes; + size_t mSampleCount; + sampleFormat mSampleFormat; + + ArrayOf mSummary256; + size_t mSummary256Bytes; + ArrayOf mSummary64k; + size_t mSummary64kBytes; + double mSumMin; + double mSumMax; + double mSumRms; + + const char *columns = + "sampleformat, summin, summax, sumrms, summary256, summary64k, samples"; + + friend class ProjectFileIO; +#if defined(WORDS_BIGENDIAN) +#error All sample block data is little endian...big endian not yet supported +#endif +}; + +#endif diff --git a/src/Sequence.cpp b/src/Sequence.cpp index abe4c2fa3..e1c121cac 100644 --- a/src/Sequence.cpp +++ b/src/Sequence.cpp @@ -40,34 +40,30 @@ #include #include -#include "DirManager.h" - -#include "blockfile/SilentBlockFile.h" -#include "blockfile/SimpleBlockFile.h" - +#include "ProjectFileIO.h" +#include "SampleBlock.h" #include "InconsistencyException.h" - #include "widgets/AudacityMessageBox.h" size_t Sequence::sMaxDiskBlockSize = 1048576; // Sequence methods -Sequence::Sequence(const std::shared_ptr &projDirManager, sampleFormat format) - : mDirManager(projDirManager) - , mSampleFormat(format) - , mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2) - , mMaxSamples(mMinSamples * 2) +Sequence::Sequence(AudacityProject *project, sampleFormat format) +: mProject(project), + mSampleFormat(format), + mMinSamples(sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2), + mMaxSamples(mMinSamples * 2) { } // essentially a copy constructor - but you must pass in the -// current project's DirManager, because we might be copying -// from one project to another -Sequence::Sequence(const Sequence &orig, const std::shared_ptr &projDirManager) - : mDirManager(projDirManager) - , mSampleFormat(orig.mSampleFormat) - , mMinSamples(orig.mMinSamples) - , mMaxSamples(orig.mMaxSamples) +// current project, because we might be copying from one +// project to another +Sequence::Sequence(const Sequence &orig, AudacityProject *project) +: mProject(project), + mSampleFormat(orig.mSampleFormat), + mMinSamples(orig.mMinSamples), + mMaxSamples(orig.mMaxSamples) { Paste(0, &orig); } @@ -89,7 +85,7 @@ size_t Sequence::GetIdealBlockSize() const bool Sequence::Lock() { for (unsigned int i = 0; i < mBlock.size(); i++) - mBlock[i].f->Lock(); + mBlock[i].sb->Lock(); return true; } @@ -97,7 +93,7 @@ bool Sequence::Lock() bool Sequence::CloseLock() { for (unsigned int i = 0; i < mBlock.size(); i++) - mBlock[i].f->CloseLock(); + mBlock[i].sb->CloseLock(); return true; } @@ -105,7 +101,7 @@ bool Sequence::CloseLock() bool Sequence::Unlock() { for (unsigned int i = 0; i < mBlock.size(); i++) - mBlock[i].f->Unlock(); + mBlock[i].sb->Unlock(); return true; } @@ -197,8 +193,8 @@ bool Sequence::ConvertToSampleFormat(sampleFormat format) for (size_t i = 0, nn = mBlock.size(); i < nn; i++) { SeqBlock &oldSeqBlock = mBlock[i]; - const auto &oldBlockFile = oldSeqBlock.f; - const auto len = oldBlockFile->GetLength(); + const auto &oldBlockFile = oldSeqBlock.sb; + const auto len = oldBlockFile->GetSampleCount(); ensureSampleBufferSize(bufferOld, oldFormat, oldSize, len); Read(bufferOld.ptr(), oldFormat, oldSeqBlock, 0, len, true); @@ -216,7 +212,7 @@ bool Sequence::ConvertToSampleFormat(sampleFormat format) // Using Blockify will handle the cases where len > the NEW mMaxSamples. Previous code did not. const auto blockstart = oldSeqBlock.start; - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlockArray, blockstart, bufferNew.ptr(), len); } } @@ -258,7 +254,7 @@ std::pair Sequence::GetMinMax( // already in memory. for (unsigned b = block0 + 1; b < block1; ++b) { - auto results = mBlock[b].f->GetMinMaxRMS(mayThrow); + auto results = mBlock[b].sb->GetMinMaxRMS(); if (results.min < min) min = results.min; @@ -272,20 +268,20 @@ std::pair Sequence::GetMinMax( // If not, we need read some samples and summaries from disk. { const SeqBlock &theBlock = mBlock[block0]; - const auto &theFile = theBlock.f; - auto results = theFile->GetMinMaxRMS(mayThrow); + const auto &theFile = theBlock.sb; + auto results = theFile->GetMinMaxRMS(); if (results.min < min || results.max > max) { // start lies within theBlock: auto s0 = ( start - theBlock.start ).as_size_t(); const auto maxl0 = ( // start lies within theBlock: - theBlock.start + theFile->GetLength() - start + theBlock.start + theFile->GetSampleCount() - start ).as_size_t(); wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19 const auto l0 = limitSampleBufferSize ( maxl0, len ); - results = theFile->GetMinMaxRMS(s0, l0, mayThrow); + results = theFile->GetMinMaxRMS(s0, l0); if (results.min < min) min = results.min; if (results.max > max) @@ -296,8 +292,8 @@ std::pair Sequence::GetMinMax( if (block1 > block0) { const SeqBlock &theBlock = mBlock[block1]; - const auto &theFile = theBlock.f; - auto results = theFile->GetMinMaxRMS(mayThrow); + const auto &theFile = theBlock.sb; + auto results = theFile->GetMinMaxRMS(); if (results.min < min || results.max > max) { @@ -305,7 +301,7 @@ std::pair Sequence::GetMinMax( const auto l0 = ( start + len - theBlock.start ).as_size_t(); wxASSERT(l0 <= mMaxSamples); // Vaughan, 2011-10-19 - results = theFile->GetMinMaxRMS(0, l0, mayThrow); + results = theFile->GetMinMaxRMS(0, l0); if (results.min < min) min = results.min; if (results.max > max) @@ -334,10 +330,10 @@ float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const // already in memory. for (unsigned b = block0 + 1; b < block1; b++) { const SeqBlock &theBlock = mBlock[b]; - const auto &theFile = theBlock.f; - auto results = theFile->GetMinMaxRMS(mayThrow); + const auto &sb = theBlock.sb; + auto results = sb->GetMinMaxRMS(); - const auto fileLen = theFile->GetLength(); + const auto fileLen = sb->GetSampleCount(); const auto blockRMS = results.RMS; sumsq += blockRMS * blockRMS * fileLen; length += fileLen; @@ -348,16 +344,16 @@ float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const // If not, we need read some samples and summaries from disk. { const SeqBlock &theBlock = mBlock[block0]; - const auto &theFile = theBlock.f; + const auto &sb = theBlock.sb; // start lies within theBlock auto s0 = ( start - theBlock.start ).as_size_t(); // start lies within theBlock const auto maxl0 = - (theBlock.start + theFile->GetLength() - start).as_size_t(); + (theBlock.start + sb->GetSampleCount() - start).as_size_t(); wxASSERT(maxl0 <= mMaxSamples); // Vaughan, 2011-10-19 const auto l0 = limitSampleBufferSize( maxl0, len ); - auto results = theFile->GetMinMaxRMS(s0, l0, mayThrow); + auto results = sb->GetMinMaxRMS(s0, l0); const auto partialRMS = results.RMS; sumsq += partialRMS * partialRMS * l0; length += l0; @@ -365,13 +361,13 @@ float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const if (block1 > block0) { const SeqBlock &theBlock = mBlock[block1]; - const auto &theFile = theBlock.f; + const auto &sb = theBlock.sb; // start + len - 1 lies within theBlock const auto l0 = ( start + len - theBlock.start ).as_size_t(); wxASSERT(l0 <= mMaxSamples); // PRL: I think Vaughan missed this - auto results = theFile->GetMinMaxRMS(0, l0, mayThrow); + auto results = sb->GetMinMaxRMS(0, l0); const auto partialRMS = results.RMS; sumsq += partialRMS * partialRMS * l0; length += l0; @@ -385,7 +381,7 @@ float Sequence::GetRMS(sampleCount start, sampleCount len, bool mayThrow) const std::unique_ptr Sequence::Copy(sampleCount s0, sampleCount s1) const { - auto dest = std::make_unique(mDirManager, mSampleFormat); + auto dest = std::make_unique(mProject, mSampleFormat); if (s0 >= s1 || s0 >= mNumSamples || s1 < 0) { return dest; } @@ -411,11 +407,11 @@ std::unique_ptr Sequence::Copy(sampleCount s0, sampleCount s1) const const SeqBlock &block0 = mBlock[b0]; if (s0 != block0.start) { - const auto &file = block0.f; + const auto &sb = block0.sb; // Nonnegative result is length of block0 or less: blocklen = - ( std::min(s1, block0.start + file->GetLength()) - s0 ).as_size_t(); - wxASSERT(file->IsAlias() || (blocklen <= (int)mMaxSamples)); // Vaughan, 2012-02-29 + ( std::min(s1, block0.start + sb->GetSampleCount()) - s0 ).as_size_t(); + wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29 ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen); Get(b0, buffer.ptr(), mSampleFormat, s0, blocklen, true); @@ -426,24 +422,24 @@ std::unique_ptr Sequence::Copy(sampleCount s0, sampleCount s1) const // If there are blocks in the middle, copy the blockfiles directly for (int bb = b0 + 1; bb < b1; ++bb) - AppendBlock(*dest->mDirManager, dest->mBlock, dest->mNumSamples, mBlock[bb]); + AppendBlock(dest->mBlock, dest->mNumSamples, mBlock[bb]); // Increase ref count or duplicate file // Do the last block if (b1 > b0) { const SeqBlock &block = mBlock[b1]; - const auto &file = block.f; + const auto &sb = block.sb; // s1 is within block: blocklen = (s1 - block.start).as_size_t(); - wxASSERT(file->IsAlias() || (blocklen <= (int)mMaxSamples)); // Vaughan, 2012-02-29 - if (blocklen < (int)file->GetLength()) { + wxASSERT(blocklen <= (int)mMaxSamples); // Vaughan, 2012-02-29 + if (blocklen < (int)sb->GetSampleCount()) { ensureSampleBufferSize(buffer, mSampleFormat, bufferSize, blocklen); Get(b1, buffer.ptr(), mSampleFormat, block.start, blocklen, true); dest->Append(buffer.ptr(), mSampleFormat, blocklen); } else // Special case, copy exactly - AppendBlock(*dest->mDirManager, dest->mBlock, dest->mNumSamples, block); + AppendBlock(dest->mBlock, dest->mNumSamples, block); // Increase ref count or duplicate file } @@ -457,16 +453,6 @@ namespace { { return numSamples > wxLL(9223372036854775807); } - - BlockFilePtr NewSimpleBlockFile( DirManager &dm, - samplePtr sampleData, size_t sampleLen, - sampleFormat format) - { - return dm.NewBlockFile( [&]( wxFileNameWrapper filePath ) { - return make_blockfile( - std::move(filePath), sampleData, sampleLen, format); - } ); - } } void Sequence::Paste(sampleCount s, const Sequence *src) @@ -513,7 +499,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) const size_t numBlocks = mBlock.size(); if (numBlocks == 0 || - (s == mNumSamples && mBlock.back().f->GetLength() >= mMinSamples)) { + (s == mNumSamples && mBlock.back().sb->GetSampleCount() >= mMinSamples)) { // Special case: this track is currently empty, or it's safe to append // onto the end because the current last block is longer than the // minimum size @@ -524,7 +510,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) for (unsigned int i = 0; i < srcNumBlocks; i++) // AppendBlock may throw for limited disk space, if pasting from // one project into another. - AppendBlock(*mDirManager, newBlock, samples, srcBlock[i]); + AppendBlock(newBlock, samples, srcBlock[i]); // Increase ref count or duplicate file CommitChangesIfConsistent @@ -535,7 +521,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) const int b = (s == mNumSamples) ? mBlock.size() - 1 : FindBlock(s); wxASSERT((b >= 0) && (b < (int)numBlocks)); SeqBlock *const pBlock = &mBlock[b]; - const auto length = pBlock->f->GetLength(); + const auto length = pBlock->sb->GetSampleCount(); const auto largerBlockLen = addedLen + length; // PRL: when insertion point is the first sample of a block, // and the following test fails, perhaps we could test @@ -559,17 +545,16 @@ void Sequence::Paste(sampleCount s, const Sequence *src) mSampleFormat, block, splitPoint, length - splitPoint, true); - auto file = - NewSimpleBlockFile( *mDirManager, - // largerBlockLen is not more than mMaxSamples... - buffer.ptr(), largerBlockLen.as_size_t(), mSampleFormat); + // largerBlockLen is not more than mMaxSamples... + block.sb = SampleBlock::Create(mProject, + buffer.ptr(), + largerBlockLen.as_size_t(), + mSampleFormat); // Don't make a duplicate array. We can still give STRONG-GUARANTEE // if we modify only one block in place. // use NOFAIL-GUARANTEE in remaining steps - block.f = file; - for (unsigned int i = b + 1; i < numBlocks; i++) mBlock[i].start += addedLen; @@ -590,7 +575,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) newBlock.insert(newBlock.end(), mBlock.begin(), mBlock.begin() + b); SeqBlock &splitBlock = mBlock[b]; - auto splitLen = splitBlock.f->GetLength(); + auto splitLen = splitBlock.sb->GetSampleCount(); // s lies within splitBlock auto splitPoint = ( s - splitBlock.start ).as_size_t(); @@ -610,7 +595,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) splitBlock, splitPoint, splitLen - splitPoint, true); - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlock, splitBlock.start, sumBuffer.ptr(), sum); } else { @@ -621,14 +606,14 @@ void Sequence::Paste(sampleCount s, const Sequence *src) // half of the split block. const auto srcFirstTwoLen = - srcBlock[0].f->GetLength() + srcBlock[1].f->GetLength(); + srcBlock[0].sb->GetSampleCount() + srcBlock[1].sb->GetSampleCount(); const auto leftLen = splitPoint + srcFirstTwoLen; const SeqBlock &penultimate = srcBlock[srcNumBlocks - 2]; const auto srcLastTwoLen = - penultimate.f->GetLength() + - srcBlock[srcNumBlocks - 1].f->GetLength(); - const auto rightSplit = splitBlock.f->GetLength() - splitPoint; + penultimate.sb->GetSampleCount() + + srcBlock[srcNumBlocks - 1].sb->GetSampleCount(); + const auto rightSplit = splitBlock.sb->GetSampleCount() - splitPoint; const auto rightLen = rightSplit + srcLastTwoLen; SampleBuffer sampleBuffer(std::max(leftLen, rightLen), mSampleFormat); @@ -637,14 +622,12 @@ void Sequence::Paste(sampleCount s, const Sequence *src) src->Get(0, sampleBuffer.ptr() + splitPoint*sampleSize, mSampleFormat, 0, srcFirstTwoLen, true); - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlock, splitBlock.start, sampleBuffer.ptr(), leftLen); for (i = 2; i < srcNumBlocks - 2; i++) { const SeqBlock &block = srcBlock[i]; - auto file = mDirManager->CopyBlockFile(block.f); - // We can assume file is not null - newBlock.push_back(SeqBlock(file, block.start + s)); + newBlock.push_back(SeqBlock(block.sb, block.start + s)); } auto lastStart = penultimate.start; @@ -653,7 +636,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src) Read(sampleBuffer.ptr() + srcLastTwoLen * sampleSize, mSampleFormat, splitBlock, splitPoint, rightSplit, true); - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlock, s + lastStart, sampleBuffer.ptr(), rightLen); } @@ -687,7 +670,7 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len) // We make use of a SilentBlockFile, which takes up no // space on disk. - Sequence sTrack(mDirManager, mSampleFormat); + Sequence sTrack(mProject, mSampleFormat); auto idealSamples = GetIdealBlockSize(); @@ -698,19 +681,21 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len) auto nBlocks = (len + idealSamples - 1) / idealSamples; sTrack.mBlock.reserve(nBlocks.as_size_t()); - BlockFilePtr silentFile {}; - if (len >= idealSamples) - silentFile = make_blockfile(idealSamples); - while (len >= idealSamples) { - sTrack.mBlock.push_back(SeqBlock(silentFile, pos)); + if (len >= idealSamples) { + auto silentFile = SampleBlock::CreateSilent(mProject, + idealSamples, + mSampleFormat); + while (len >= idealSamples) { + sTrack.mBlock.push_back(SeqBlock(silentFile, pos)); - pos += idealSamples; - len -= idealSamples; + pos += idealSamples; + len -= idealSamples; + } } if (len != 0) { + // len is not more than idealSamples: sTrack.mBlock.push_back(SeqBlock( - // len is not more than idealSamples: - make_blockfile( len.as_size_t() ), pos)); + SampleBlock::CreateSilent(mProject, len.as_size_t(), mSampleFormat), pos)); pos += len; } @@ -720,22 +705,19 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len) Paste(s0, &sTrack); } -void Sequence::AppendBlock - (DirManager &mDirManager, - BlockArray &mBlock, sampleCount &mNumSamples, const SeqBlock &b) +void Sequence::AppendBlock(BlockArray &mBlock, sampleCount &mNumSamples, const SeqBlock &b) { // Quick check to make sure that it doesn't overflow - if (Overflows((mNumSamples.as_double()) + ((double)b.f->GetLength()))) + if (Overflows((mNumSamples.as_double()) + ((double)b.sb->GetSampleCount()))) THROW_INCONSISTENCY_EXCEPTION; - SeqBlock newBlock( - mDirManager.CopyBlockFile(b.f), // Bump ref count if not locked, else copy - mNumSamples - ); - // We can assume newBlock.f is not null + // Bump ref count + SeqBlock newBlock(b.sb, mNumSamples); + + // We can assume newBlock.sb is not null mBlock.push_back(newBlock); - mNumSamples += newBlock.f->GetLength(); + mNumSamples += newBlock.sb->GetSampleCount(); // Don't do a consistency check here because this // function gets called in an inner loop. @@ -762,11 +744,11 @@ size_t Sequence::GetBestBlockSize(sampleCount start) const const SeqBlock &block = mBlock[b]; // start is in block: - auto result = (block.start + block.f->GetLength() - start).as_size_t(); + auto result = (block.start + block.sb->GetSampleCount() - start).as_size_t(); decltype(result) length; while(result < mMinSamples && b+1GetLength()) + result) <= mMaxSamples) { + ((length = mBlock[b+1].sb->GetSampleCount()) + result) <= mMaxSamples) { b++; result += length; } @@ -779,71 +761,63 @@ size_t Sequence::GetBestBlockSize(sampleCount start) const bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { /* handle waveblock tag and its attributes */ - if (!wxStrcmp(tag, wxT("waveblock"))) { + if (!wxStrcmp(tag, wxT("waveblock"))) + { SeqBlock wb; - // loop through attrs, which is a null-terminated list of - // attribute-value pairs - while(*attrs) { + // Give SampleBlock a go at the attributes first + wb.sb = SampleBlock::CreateFromXML(mProject, mSampleFormat, attrs); + if (wb.sb == nullptr) + { + mErrorOpening = true; + return false; + } + + // loop through attrs, which is a null-terminated list of attribute-value pairs + while(*attrs) + { const wxChar *attr = *attrs++; const wxChar *value = *attrs++; - long long nValue = 0; - if (!value) - break; - - // Both these attributes have non-negative integer counts of samples, so - // we can test & convert here, making sure that values > 2^31 are OK - // because long clips will need them. - const wxString strValue = value; - if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0)) { - mErrorOpening = true; - wxLogWarning( - wxT(" Sequence has bad %s attribute value, %s, that should be a positive integer."), - attr, strValue); - return false; + break; } - if (!wxStrcmp(attr, wxT("start"))) - wb.start = nValue; + long long nValue = 0; - // Vaughan, 2011-10-10: I don't think we ever write a "len" attribute for "waveblock" tag, - // so I think this is actually legacy code, or something intended, but not completed. - // Anyway, might as well leave this code in, especially now that it has the check - // against mMaxSamples. - if (!wxStrcmp(attr, wxT("len"))) + const wxString strValue = value; // promote string, we need this for all + + if (wxStrcmp(attr, wxT("start")) == 0) { - // mMaxSamples should already have been set by calls to the "sequence" clause below. - // The check intended here was already done in DirManager::HandleXMLTag(), where - // it let the block be built, then checked against mMaxSamples, and deleted the block - // if the size of the block is bigger than mMaxSamples. - if (static_cast(nValue) > mMaxSamples) + // This attribute is a sample offset, so can be 64bit + if (!XMLValueChecker::IsGoodInt64(strValue) || !strValue.ToLongLong(&nValue) || (nValue < 0)) { mErrorOpening = true; return false; } - mDirManager->SetLoadingBlockLength(nValue); + + wb.start = nValue; } - } // while + } mBlock.push_back(wb); - auto index = mBlock.size() - 1; - mDirManager->SetLoadingTarget( - [this, index] () -> BlockFilePtr& { return mBlock[index].f; } ); return true; } /* handle sequence tag and its attributes */ - if (!wxStrcmp(tag, wxT("sequence"))) { - while(*attrs) { + if (!wxStrcmp(tag, wxT("sequence"))) + { + while(*attrs) + { const wxChar *attr = *attrs++; const wxChar *value = *attrs++; if (!value) + { break; + } long long nValue = 0; @@ -857,6 +831,7 @@ bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs) mErrorOpening = true; return false; } + // Dominic, 12/10/2006: // Let's check that maxsamples is >= 1024 and <= 64 * 1024 * 1024 // - that's a pretty wide range of reasonable values. @@ -868,10 +843,6 @@ bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs) // nValue is now safe for size_t mMaxSamples = nValue; - - // PRL: Is the following really okay? DirManager might be shared across projects! - // PRL: Yes, because it only affects DirManager's behavior in opening the project. - mDirManager->SetLoadingMaxSamples(mMaxSamples); } else if (!wxStrcmp(attr, wxT("sampleformat"))) { @@ -896,15 +867,6 @@ bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs) } } // while - //// Both mMaxSamples and mSampleFormat should have been set. - //// Check that mMaxSamples is right for mSampleFormat, using the calculations from the constructor. - //if ((mMinSamples != sMaxDiskBlockSize / SAMPLE_SIZE(mSampleFormat) / 2) || - // (mMaxSamples != mMinSamples * 2)) - //{ - // mErrorOpening = true; - // return false; - //} - return true; } @@ -914,63 +876,35 @@ bool Sequence::HandleXMLTag(const wxChar *tag, const wxChar **attrs) void Sequence::HandleXMLEndTag(const wxChar *tag) { if (wxStrcmp(tag, wxT("sequence")) != 0) + { return; - - // Make sure that the sequence is valid. - // First, replace missing blockfiles with SilentBlockFiles - for (unsigned b = 0, nn = mBlock.size(); b < nn; b++) { - SeqBlock &block = mBlock[b]; - if (!block.f) { - sampleCount len; - - if (b < nn - 1) - len = mBlock[b+1].start - block.start; - else - len = mNumSamples - block.start; - - if (len > mMaxSamples) - { - // This could be why the blockfile failed, so limit - // the silent replacement to mMaxSamples. - wxLogWarning( - wxT(" Sequence has missing block file with length %s > mMaxSamples %s.\n Setting length to mMaxSamples. This will likely cause some block files to be considered orphans."), - // PRL: Why bother with Internat when the above is just wxT? - Internat::ToString(len.as_double(), 0), - Internat::ToString((double)mMaxSamples, 0)); - len = mMaxSamples; - } - // len is at most mMaxSamples: - block.f = make_blockfile( len.as_size_t() ); - wxLogWarning( - wxT("Gap detected in project file. Replacing missing block file with silence.")); - mErrorOpening = true; - } } - // Next, make sure that start times and lengths are consistent + // Make sure that the sequence is valid. + + // Make sure that start times and lengths are consistent sampleCount numSamples = 0; - for (unsigned b = 0, nn = mBlock.size(); b < nn; b++) { + for (unsigned b = 0, nn = mBlock.size(); b < nn; b++) + { SeqBlock &block = mBlock[b]; - if (block.start != numSamples) { - wxString sFileAndExtension = block.f->GetFileName().name.GetFullName(); - if (sFileAndExtension.empty()) - sFileAndExtension = wxT("(replaced with silence)"); - else - sFileAndExtension = wxT("\"") + sFileAndExtension + wxT("\""); + if (block.start != numSamples) + { wxLogWarning( wxT("Gap detected in project file.\n") - wxT(" Start (%s) for block file %s is not one sample past end of previous block (%s).\n") + wxT(" Start (%s) for block file %d is not one sample past end of previous block (%s).\n") wxT(" Moving start so blocks are contiguous."), // PRL: Why bother with Internat when the above is just wxT? Internat::ToString(block.start.as_double(), 0), - sFileAndExtension, + block.sb->GetBlockID(), Internat::ToString(numSamples.as_double(), 0)); block.start = numSamples; mErrorOpening = true; } - numSamples += block.f->GetLength(); + numSamples += block.sb->GetSampleCount(); } - if (mNumSamples != numSamples) { + + if (mNumSamples != numSamples) + { wxLogWarning( wxT("Gap detected in project file. Correcting sequence sample count from %s to %s."), // PRL: Why bother with Internat when the above is just wxT? @@ -984,11 +918,11 @@ void Sequence::HandleXMLEndTag(const wxChar *tag) XMLTagHandler *Sequence::HandleXMLChild(const wxChar *tag) { if (!wxStrcmp(tag, wxT("waveblock"))) + { return this; - else { - mDirManager->SetLoadingFormat(mSampleFormat); - return mDirManager.get(); } + + return nullptr; } // Throws exceptions rather than reporting errors. @@ -1007,10 +941,7 @@ void Sequence::WriteXML(XMLWriter &xmlFile) const const SeqBlock &bb = mBlock[b]; // See http://bugzilla.audacityteam.org/show_bug.cgi?id=451. - // Also, don't check against mMaxSamples for AliasBlockFiles, because if you convert sample format, - // mMaxSample gets changed to match the format, but the number of samples in the aliased file - // has not changed (because sample format conversion was not actually done in the aliased file). - if (!bb.f->IsAlias() && (bb.f->GetLength() > mMaxSamples)) + if (bb.sb->GetSampleCount() > mMaxSamples) { // PRL: Bill observed this error. Not sure how it was caused. // I have added code in ConsistencyCheck that should abort the @@ -1025,13 +956,13 @@ void Sequence::WriteXML(XMLWriter &xmlFile) const XO("Warning - Truncating Overlong Block File"), wxICON_EXCLAMATION | wxOK); wxLogWarning(sMsg.Translation()); //Debug? - bb.f->SetLength(mMaxSamples); +// bb.sb->SetLength(mMaxSamples); } xmlFile.StartTag(wxT("waveblock")); xmlFile.WriteAttr(wxT("start"), bb.start.as_long_long() ); - bb.f->SaveXML(xmlFile); + bb.sb->SaveXML(xmlFile); xmlFile.EndTag(wxT("waveblock")); } @@ -1060,7 +991,7 @@ int Sequence::FindBlock(sampleCount pos) const guess = std::min(hi - 1, lo + size_t(frac * (hi - lo))); const SeqBlock &block = mBlock[guess]; - wxASSERT(block.f->GetLength() > 0); + wxASSERT(block.sb->GetSampleCount() > 0); wxASSERT(lo <= guess && guess < hi && lo < hi); if (pos < block.start) { @@ -1069,7 +1000,7 @@ int Sequence::FindBlock(sampleCount pos) const hiSamples = block.start; } else { - const sampleCount nextStart = block.start + block.f->GetLength(); + const sampleCount nextStart = block.start + block.sb->GetSampleCount(); if (pos < nextStart) break; else { @@ -1083,7 +1014,7 @@ int Sequence::FindBlock(sampleCount pos) const const int rval = guess; wxASSERT(rval >= 0 && rval < numBlocks && pos >= mBlock[rval].start && - pos < mBlock[rval].start + mBlock[rval].f->GetLength()); + pos < mBlock[rval].start + mBlock[rval].sb->GetSampleCount()); return rval; } @@ -1093,12 +1024,12 @@ bool Sequence::Read(samplePtr buffer, sampleFormat format, const SeqBlock &b, size_t blockRelativeStart, size_t len, bool mayThrow) { - const auto &f = b.f; + const auto &sb = b.sb; - wxASSERT(blockRelativeStart + len <= f->GetLength()); + wxASSERT(blockRelativeStart + len <= sb->GetSampleCount()); // Either throws, or of !mayThrow, tells how many were really read - auto result = f->ReadData(buffer, format, blockRelativeStart, len, mayThrow); + auto result = sb->GetSamples(buffer, format, blockRelativeStart, len); if (result != len) { @@ -1137,7 +1068,7 @@ bool Sequence::Get(int b, samplePtr buffer, sampleFormat format, // start is in block const auto bstart = (start - block.start).as_size_t(); // bstart is not more than block length - const auto blen = std::min(len, block.f->GetLength() - bstart); + const auto blen = std::min(len, block.sb->GetSampleCount() - bstart); if (! Read(buffer, format, block, bstart, blen, mayThrow) ) result = false; @@ -1184,7 +1115,7 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format, SeqBlock &block = newBlock.back(); // start is within block const auto bstart = ( start - block.start ).as_size_t(); - const auto fileLength = block.f->GetLength(); + const auto fileLength = block.sb->GetSampleCount(); // the std::min is a guard against inconsistent Sequence const auto blen = @@ -1231,16 +1162,17 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format, else ClearSamples(scratch.ptr(), mSampleFormat, bstart, blen); - block.f = NewSimpleBlockFile( *mDirManager, - scratch.ptr(), fileLength, mSampleFormat); + block.sb = SampleBlock::Create(mProject, + scratch.ptr(), + fileLength, + mSampleFormat); } else { // Avoid reading the disk when the replacement is total if (useBuffer) - block.f = NewSimpleBlockFile( *mDirManager, - useBuffer, fileLength, mSampleFormat); + block.sb = SampleBlock::Create(mProject, useBuffer, fileLength, mSampleFormat); else - block.f = make_blockfile(fileLength); + block.sb = SampleBlock::CreateSilent(mProject, fileLength, mSampleFormat); } // blen might be zero for inconsistent Sequence... @@ -1340,7 +1272,7 @@ bool Sequence::GetWaveDisplay(float *min, float *max, float *rms, int* bl, // are in the display. const SeqBlock &seqBlock = mBlock[b]; const auto start = seqBlock.start; - nextSrcX = std::min(s1, start + seqBlock.f->GetLength()); + nextSrcX = std::min(s1, start + seqBlock.sb->GetSampleCount()); // The column for pixel p covers samples from // where[p] up to but excluding where[p + 1]. @@ -1415,13 +1347,13 @@ bool Sequence::GetWaveDisplay(float *min, float *max, float *rms, int* bl, // Read triples // Ignore the return value. // This function fills with zeroes if read fails - seqBlock.f->Read256(temp.get(), startPosition, num); + seqBlock.sb->GetSummary256(temp.get(), startPosition, num); break; case 65536: // Read triples // Ignore the return value. // This function fills with zeroes if read fails - seqBlock.f->Read64K(temp.get(), startPosition, num); + seqBlock.sb->GetSummary64k(temp.get(), startPosition, num); break; } @@ -1507,15 +1439,14 @@ size_t Sequence::GetIdealAppendLen() const if (numBlocks == 0) return max; - const auto lastBlockLen = mBlock.back().f->GetLength(); + const auto lastBlockLen = mBlock.back().sb->GetSampleCount(); if (lastBlockLen >= max) return max; else return max - lastBlockLen; } -void Sequence::Append(samplePtr buffer, sampleFormat format, - size_t len, XMLWriter* blockFileLog /*=NULL*/) +void Sequence::Append(samplePtr buffer, sampleFormat format, size_t len) // STRONG-GUARANTEE { if (len == 0) @@ -1531,13 +1462,13 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, // If the last block is not full, we need to add samples to it int numBlocks = mBlock.size(); SeqBlock *pLastBlock; - decltype(pLastBlock->f->GetLength()) length; + decltype(pLastBlock->sb->GetSampleCount()) length; size_t bufferSize = mMaxSamples; SampleBuffer buffer2(bufferSize, mSampleFormat); bool replaceLast = false; if (numBlocks > 0 && (length = - (pLastBlock = &mBlock.back())->f->GetLength()) < mMinSamples) { + (pLastBlock = &mBlock.back())->sb->GetSampleCount()) < mMinSamples) { // Enlarge a sub-minimum block at the end const SeqBlock &lastBlock = *pLastBlock; const auto addLen = std::min(mMaxSamples - length, len); @@ -1551,17 +1482,11 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, addLen); const auto newLastBlockLen = length + addLen; - - SeqBlock newLastBlock( - NewSimpleBlockFile( *mDirManager, - buffer2.ptr(), newLastBlockLen, mSampleFormat), - lastBlock.start - ); - - if (blockFileLog) - // shouldn't throw, because XMLWriter is not XMLFileWriter - static_cast< SimpleBlockFile * >( &*newLastBlock.f ) - ->SaveXML( *blockFileLog ); + SampleBlockPtr pBlock = SampleBlock::Create(mProject, + buffer2.ptr(), + newLastBlockLen, + mSampleFormat); + SeqBlock newLastBlock(pBlock, lastBlock.start); newBlock.push_back( newLastBlock ); @@ -1575,22 +1500,16 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, while (len) { const auto idealSamples = GetIdealBlockSize(); const auto addedLen = std::min(idealSamples, len); - BlockFilePtr pFile; + SampleBlockPtr pBlock; if (format == mSampleFormat) { - pFile = NewSimpleBlockFile( *mDirManager, - buffer, addedLen, mSampleFormat); + pBlock = SampleBlock::Create(mProject, buffer, addedLen, mSampleFormat); } else { CopySamples(buffer, format, buffer2.ptr(), mSampleFormat, addedLen); - pFile = NewSimpleBlockFile( *mDirManager, - buffer2.ptr(), addedLen, mSampleFormat); + pBlock = SampleBlock::Create(mProject, buffer2.ptr(), addedLen, mSampleFormat); } - if (blockFileLog) - // shouldn't throw, because XMLWriter is not XMLFileWriter - static_cast< SimpleBlockFile * >( &*pFile )->SaveXML( *blockFileLog ); - - newBlock.push_back(SeqBlock(pFile, newNumSamples)); + newBlock.push_back(SeqBlock(pBlock, newNumSamples)); buffer += addedLen * SAMPLE_SIZE(format); newNumSamples += addedLen; @@ -1608,9 +1527,9 @@ void Sequence::Append(samplePtr buffer, sampleFormat format, #endif } -void Sequence::Blockify - (DirManager &mDirManager, size_t mMaxSamples, sampleFormat mSampleFormat, - BlockArray &list, sampleCount start, samplePtr buffer, size_t len) +void Sequence::Blockify(AudacityProject *project, + size_t mMaxSamples, sampleFormat mSampleFormat, + BlockArray &list, sampleCount start, samplePtr buffer, size_t len) { if (len <= 0) return; @@ -1625,7 +1544,7 @@ void Sequence::Blockify int newLen = ((i + 1) * len / num) - offset; samplePtr bufStart = buffer + (offset * SAMPLE_SIZE(mSampleFormat)); - b.f = NewSimpleBlockFile( mDirManager, bufStart, newLen, mSampleFormat ); + b.sb = SampleBlock::Create(project, bufStart, newLen, mSampleFormat); list.push_back(b); } @@ -1648,7 +1567,7 @@ void Sequence::Delete(sampleCount start, sampleCount len) auto sampleSize = SAMPLE_SIZE(mSampleFormat); SeqBlock *pBlock; - decltype(pBlock->f->GetLength()) length; + decltype(pBlock->sb->GetSampleCount()) length; // One buffer for reuse in various branches here SampleBuffer scratch; @@ -1659,7 +1578,7 @@ void Sequence::Delete(sampleCount start, sampleCount len) // block and the resulting length is not too small, perform the // deletion within this block: if (b0 == b1 && - (length = (pBlock = &mBlock[b0])->f->GetLength()) - len >= mMinSamples) { + (length = (pBlock = &mBlock[b0])->sb->GetSampleCount()) - len >= mMinSamples) { SeqBlock &b = *pBlock; // start is within block auto pos = ( start - b.start ).as_size_t(); @@ -1681,16 +1600,13 @@ void Sequence::Delete(sampleCount start, sampleCount len) // is not more than the length of the block ( pos + len ).as_size_t(), newLen - pos, true); - auto newFile = - NewSimpleBlockFile( *mDirManager, scratch.ptr(), newLen, mSampleFormat ); + b.sb = SampleBlock::Create(mProject, scratch.ptr(), newLen, mSampleFormat); // Don't make a duplicate array. We can still give STRONG-GUARANTEE // if we modify only one block in place. // use NOFAIL-GUARANTEE in remaining steps - b.f = newFile; - for (unsigned int j = b0 + 1; j < numBlocks; j++) mBlock[j].start -= len; @@ -1726,12 +1642,12 @@ void Sequence::Delete(sampleCount start, sampleCount len) ensureSampleBufferSize(scratch, mSampleFormat, scratchSize, preBufferLen); Read(scratch.ptr(), mSampleFormat, preBlock, 0, preBufferLen, true); auto pFile = - NewSimpleBlockFile( *mDirManager, scratch.ptr(), preBufferLen, mSampleFormat ); + SampleBlock::Create(mProject, scratch.ptr(), preBufferLen, mSampleFormat); newBlock.push_back(SeqBlock(pFile, preBlock.start)); } else { const SeqBlock &prepreBlock = mBlock[b0 - 1]; - const auto prepreLen = prepreBlock.f->GetLength(); + const auto prepreLen = prepreBlock.sb->GetSampleCount(); const auto sum = prepreLen + preBufferLen; if (!scratch.ptr()) @@ -1744,7 +1660,7 @@ void Sequence::Delete(sampleCount start, sampleCount len) preBlock, 0, preBufferLen, true); newBlock.pop_back(); - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlock, prepreBlock.start, scratch.ptr(), sum); } } @@ -1761,7 +1677,7 @@ void Sequence::Delete(sampleCount start, sampleCount len) const SeqBlock &postBlock = mBlock[b1]; // start + len - 1 lies within postBlock const auto postBufferLen = ( - (postBlock.start + postBlock.f->GetLength()) - (start + len) + (postBlock.start + postBlock.sb->GetSampleCount()) - (start + len) ).as_size_t(); if (postBufferLen) { if (postBufferLen >= mMinSamples || b1 == numBlocks - 1) { @@ -1772,12 +1688,12 @@ void Sequence::Delete(sampleCount start, sampleCount len) auto pos = (start + len - postBlock.start).as_size_t(); Read(scratch.ptr(), mSampleFormat, postBlock, pos, postBufferLen, true); auto file = - NewSimpleBlockFile( *mDirManager, scratch.ptr(), postBufferLen, mSampleFormat ); + SampleBlock::Create(mProject, scratch.ptr(), postBufferLen, mSampleFormat); newBlock.push_back(SeqBlock(file, start)); } else { SeqBlock &postpostBlock = mBlock[b1 + 1]; - const auto postpostLen = postpostBlock.f->GetLength(); + const auto postpostLen = postpostBlock.sb->GetSampleCount(); const auto sum = postpostLen + postBufferLen; if (!scratch.ptr()) @@ -1789,7 +1705,7 @@ void Sequence::Delete(sampleCount start, sampleCount len) Read(scratch.ptr() + (postBufferLen * sampleSize), mSampleFormat, postpostBlock, 0, postpostLen, true); - Blockify(*mDirManager, mMaxSamples, mSampleFormat, + Blockify(mProject, mMaxSamples, mSampleFormat, newBlock, start, scratch.ptr(), sum); b1++; } @@ -1833,8 +1749,8 @@ void Sequence::ConsistencyCheck if (pos != seqBlock.start) ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION ); - if ( seqBlock.f ) { - const auto length = seqBlock.f->GetLength(); + if ( seqBlock.sb ) { + const auto length = seqBlock.sb->GetSampleCount(); if (length > maxSamples) ex.emplace( CONSTRUCT_INCONSISTENCY_EXCEPTION ); pos += length; @@ -1927,24 +1843,20 @@ void Sequence::DebugPrintf for (i = 0; i < mBlock.size(); i++) { const SeqBlock &seqBlock = mBlock[i]; *dest += wxString::Format - (wxT(" Block %3u: start %8lld, len %8lld, refs %ld, "), + (wxT(" Block %3u: start %8lld, len %8lld, refs %ld, id %lld"), i, seqBlock.start.as_long_long(), - seqBlock.f ? (long long) seqBlock.f->GetLength() : 0, - seqBlock.f ? seqBlock.f.use_count() : 0); + seqBlock.sb ? (long long) seqBlock.sb->GetSampleCount() : 0, + seqBlock.sb ? seqBlock.sb.use_count() : 0, + seqBlock.sb ? (long long) seqBlock.sb->GetBlockID() : 0); - if (seqBlock.f) - *dest += seqBlock.f->GetFileName().name.GetFullName(); - else - *dest += wxT(""); - - if ((pos != seqBlock.start) || !seqBlock.f) + if ((pos != seqBlock.start) || !seqBlock.sb) *dest += wxT(" ERROR\n"); else *dest += wxT("\n"); - if (seqBlock.f) - pos += seqBlock.f->GetLength(); + if (seqBlock.sb) + pos += seqBlock.sb->GetSampleCount(); } if (pos != mNumSamples) *dest += wxString::Format @@ -1961,35 +1873,3 @@ size_t Sequence::GetMaxDiskBlockSize() { return sMaxDiskBlockSize; } - -void Sequence::AppendBlockFile( const BlockFileFactory &factory, size_t len ) -// STRONG-GUARANTEE -{ - // Quick check to make sure that it doesn't overflow - if (Overflows((mNumSamples.as_double()) + ((double)len))) - THROW_INCONSISTENCY_EXCEPTION; - - SeqBlock newBlock( - mDirManager->NewBlockFile( [&]( wxFileNameWrapper filePath ){ - return factory( std::move( filePath ), len ); - } ), - mNumSamples - ); - mBlock.push_back(newBlock); - mNumSamples += len; -} - -void Sequence::AppendBlockFile(const BlockFilePtr &blockFile) -{ - // We assume blockFile has the correct ref count already - - mBlock.push_back(SeqBlock(blockFile, mNumSamples)); - mNumSamples += blockFile->GetLength(); - - // PRL: I hoisted the intended consistency check out of the inner loop - // See RecordingRecoveryHandler::HandleXMLEndTag - -#ifdef VERY_SLOW_CHECKING - ConsistencyCheck(wxT("AppendBlockFile")); -#endif -} diff --git a/src/Sequence.h b/src/Sequence.h index d71765c89..acfa09280 100644 --- a/src/Sequence.h +++ b/src/Sequence.h @@ -18,31 +18,29 @@ #include "audacity/Types.h" -class BlockFile; -using BlockFilePtr = std::shared_ptr; - -class DirManager; -class wxFileNameWrapper; +class AudacityProject; +class SampleBlock; // This is an internal data structure! For advanced use only. class SeqBlock { public: - BlockFilePtr f; + using SampleBlockPtr = std::shared_ptr; + SampleBlockPtr sb; ///the sample in the global wavetrack that this block starts at. sampleCount start; SeqBlock() - : f{}, start(0) + : sb{}, start(0) {} - SeqBlock(const BlockFilePtr &f_, sampleCount start_) - : f(f_), start(start_) + SeqBlock(const SampleBlockPtr &sb_, sampleCount start_) + : sb(sb_), start(start_) {} // Construct a SeqBlock with changed start, same file SeqBlock Plus(sampleCount delta) const { - return SeqBlock(f, start + delta); + return SeqBlock(sb, start + delta); } }; class BlockArray : public std::vector {}; @@ -62,15 +60,10 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ // Constructor / Destructor / Duplicator // - Sequence(const std::shared_ptr &projDirManager, sampleFormat format); + Sequence(AudacityProject *project, sampleFormat format); - // The copy constructor and duplicate operators take a - // DirManager as a parameter, because you might be copying - // from one project to another... - Sequence(const Sequence &orig, const std::shared_ptr &projDirManager); + Sequence(const Sequence &orig, AudacityProject *project); - // Sequence cannot be copied without specifying a DirManager - Sequence(const Sequence&) PROHIBITED; Sequence& operator= (const Sequence&) PROHIBITED; ~Sequence(); @@ -87,7 +80,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ // Note that len is not size_t, because nullptr may be passed for buffer, in // which case, silence is inserted, possibly a large amount. void SetSamples(samplePtr buffer, sampleFormat format, - sampleCount start, sampleCount len); + sampleCount start, sampleCount len); // where is input, assumed to be nondecreasing, and its size is len + 1. // min, max, rms, bl are outputs, and their lengths are len. @@ -104,28 +97,13 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ void Paste(sampleCount s0, const Sequence *src); size_t GetIdealAppendLen() const; - void Append(samplePtr buffer, sampleFormat format, size_t len, - XMLWriter* blockFileLog=NULL); + void Append(samplePtr buffer, sampleFormat format, size_t len); void Delete(sampleCount start, sampleCount len); - using BlockFileFactory = - std::function< BlockFilePtr( wxFileNameWrapper, size_t /* len */ ) >; - // An overload of AppendBlockFile that passes the factory to DirManager - // which supplies it with a file name - void AppendBlockFile( const BlockFileFactory &factory, size_t len ); - - // Append a blockfile. The blockfile pointer is then "owned" by the - // sequence. This function is used by the recording log crash recovery - // code, but may be useful for other purposes. The blockfile must already - // be registered within the dir manager hash. This is the case - // when the blockfile is created using SimpleBlockFile or - // loaded from an XML file via DirManager::HandleXMLTag - void AppendBlockFile(const BlockFilePtr &blockFile); - void SetSilence(sampleCount s0, sampleCount len); void InsertSilence(sampleCount s0, sampleCount len); - const std::shared_ptr &GetDirManager() { return mDirManager; } + AudacityProject *GetProject() { return mProject; } // // XMLTagHandler callback methods for loading and saving @@ -200,7 +178,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ // Private variables // - std::shared_ptr mDirManager; + AudacityProject *mProject; BlockArray mBlock; sampleFormat mSampleFormat; @@ -219,23 +197,34 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ int FindBlock(sampleCount pos) const; - static void AppendBlock - (DirManager &dirManager, - BlockArray &blocks, sampleCount &numSamples, const SeqBlock &b); + static void AppendBlock(BlockArray &blocks, + sampleCount &numSamples, + const SeqBlock &b); - static bool Read(samplePtr buffer, sampleFormat format, - const SeqBlock &b, - size_t blockRelativeStart, size_t len, bool mayThrow); + static bool Read(samplePtr buffer, + sampleFormat format, + const SeqBlock &b, + size_t blockRelativeStart, + size_t len, + bool mayThrow); // Accumulate NEW block files onto the end of a block array. // Does not change this sequence. The intent is to use // CommitChangesIfConsistent later. - static void Blockify - (DirManager &dirManager, size_t maxSamples, sampleFormat format, - BlockArray &list, sampleCount start, samplePtr buffer, size_t len); + static void Blockify(AudacityProject *project, + size_t maxSamples, + sampleFormat format, + BlockArray &list, + sampleCount start, + samplePtr buffer, + size_t len); - bool Get(int b, samplePtr buffer, sampleFormat format, - sampleCount start, size_t len, bool mayThrow) const; + bool Get(int b, + samplePtr buffer, + sampleFormat format, + sampleCount start, + size_t len, + bool mayThrow) const; public: diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index 1428e846a..fcd15ffab 100644 --- a/src/TimeTrack.cpp +++ b/src/TimeTrack.cpp @@ -38,7 +38,7 @@ std::shared_ptr TrackFactory::NewTimeTrack() { - return std::make_shared(mDirManager, mZoomInfo); + return std::make_shared(&mProject, mZoomInfo); } static ProjectFileIORegistry::Entry registerFactory{ @@ -53,8 +53,8 @@ static ProjectFileIORegistry::Entry registerFactory{ } }; -TimeTrack::TimeTrack(const std::shared_ptr &projDirManager, const ZoomInfo *zoomInfo): - Track(projDirManager) +TimeTrack::TimeTrack(AudacityProject *project, const ZoomInfo *zoomInfo): + Track(project) , mZoomInfo(zoomInfo) { mEnvelope = std::make_unique(true, TIMETRACK_MIN, TIMETRACK_MAX, 1.0); diff --git a/src/TimeTrack.h b/src/TimeTrack.h index 70441fa60..6807e8f02 100644 --- a/src/TimeTrack.h +++ b/src/TimeTrack.h @@ -25,7 +25,7 @@ class TimeTrack final : public Track { public: - TimeTrack(const std::shared_ptr &projDirManager, const ZoomInfo *zoomInfo); + TimeTrack(AudacityProject *project, const ZoomInfo *zoomInfo); /** @brief Copy-Constructor - create a NEW TimeTrack:: which is an independent copy of the original * * Calls TimeTrack::Init() to copy the track metadata, then does a bunch of manipulations on the diff --git a/src/TimerRecordDialog.cpp b/src/TimerRecordDialog.cpp index cca796bde..cae558d99 100644 --- a/src/TimerRecordDialog.cpp +++ b/src/TimerRecordDialog.cpp @@ -40,11 +40,9 @@ #include #include //HaveFilesToRecover()); - } TimerRecordDialog::~TimerRecordDialog() @@ -305,11 +299,13 @@ void TimerRecordDialog::OnTimeText_Duration(wxCommandEvent& WXUNUSED(event)) // New events for timer recording automation void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(event)) { + auto &projectFileIO = ProjectFileIO::Get(mProject); + wxString fName = FileNames::SelectFile(FileNames::Operation::Export, XO("Save Timer Recording As"), m_fnAutoSaveFile.GetPath(), m_fnAutoSaveFile.GetFullName(), - wxT("aup"), + wxT("aup3"), { FileNames::AudacityProjects }, wxFD_SAVE | wxRESIZE_BORDER, this); @@ -317,11 +313,9 @@ void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(even if (fName.empty()) return; - AudacityProject* pProject = &mProject; - // If project already exists then abort - we do not allow users to overwrite an existing project // unless it is the current project. - if (wxFileExists(fName) && (pProject->GetFileName() != fName)) { + if (wxFileExists(fName) && (projectFileIO.GetFileName() != fName)) { AudacityMessageDialog m( nullptr, XO("The selected file name could not be used\nfor Timer Recording because it \ @@ -334,10 +328,10 @@ would overwrite another project.\nPlease try again and select an original name." // Set this boolean to false so we now do a SaveAs at the end of the recording // unless we're saving the current project. - m_bProjectAlreadySaved = pProject->GetFileName() == fName? true : false; + m_bProjectAlreadySaved = projectFileIO.GetFileName() == fName? true : false; m_fnAutoSaveFile = fName; - m_fnAutoSaveFile.SetExt(wxT("aup")); + m_fnAutoSaveFile.SetExt(wxT("aup3")); this->UpdateTextBoxControls(); } @@ -411,8 +405,7 @@ void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event)) // We don't stop the user from starting the recording // as its possible that they plan to free up some // space before the recording begins - AudacityProject* pProject = &mProject; - auto &projectManager = ProjectManager::Get( *pProject ); + auto &projectManager = ProjectManager::Get( mProject ); // How many minutes do we have left on the disc? int iMinsLeft = projectManager.GetEstimatedRecordingMinsLeftOnDisk(); @@ -481,53 +474,10 @@ void TimerRecordDialog::UpdateTextBoxControls() { } } -// Copied from AutoRecovery.cpp - for use with Timer Recording Improvements -bool TimerRecordDialog::HaveFilesToRecover() -{ - wxDir dir(FileNames::AutoSaveDir()); - if (!dir.IsOpened()) { - AudacityMessageBox( - XO("Could not enumerate files in auto save directory."), - XO("Error"), - wxICON_STOP); - return false; - } - - wxString filename; - bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES); - - return c; -} - -bool TimerRecordDialog::RemoveAllAutoSaveFiles() -{ - FilePaths files; - wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files, - wxT("*.autosave"), wxDIR_FILES); - - for (unsigned int i = 0; i < files.size(); i++) - { - if (!wxRemoveFile(files[i])) - { - // I don't think this error message is actually useful. - // -dmazzoni - //AudacityMessageBox( - // XO("Could not remove auto save file: %s)".Format( files[i] ), - // XO("Error"), - // wxICON_STOP); - return false; - } - } - - return true; -} - /// Runs the wait for start dialog. Returns -1 if the user clicks stop while we are recording /// or if the post recording actions fail. int TimerRecordDialog::RunWaitDialog() { - AudacityProject* pProject = &mProject; - auto updateResult = ProgressResult::Success; if (m_DateTime_Start > wxDateTime::UNow()) @@ -538,7 +488,7 @@ int TimerRecordDialog::RunWaitDialog() return POST_TIMER_RECORD_CANCEL_WAIT; } else { // Record for specified time. - ProjectAudioManager::Get( *pProject ).OnRecord(false); + ProjectAudioManager::Get( mProject ).OnRecord(false); bool bIsRecording = true; auto sPostAction = Verbatim( @@ -587,7 +537,7 @@ int TimerRecordDialog::RunWaitDialog() // Must do this AFTER the timer project dialog has been deleted to ensure the application // responds to the AUDIOIO events...see not about bug #334 in the ProgressDialog constructor. - ProjectAudioManager::Get( *pProject ).Stop(); + ProjectAudioManager::Get( mProject ).Stop(); // Let the caller handle cancellation or failure from recording progress. if (updateResult == ProgressResult::Cancelled || updateResult == ProgressResult::Failed) @@ -607,8 +557,6 @@ int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { // Finally, if there is no post-record action selected then we output // a dialog detailing what has been carried out instead. - AudacityProject* pProject = &mProject; - bool bSaveOK = false; bool bExportOK = false; int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection(); @@ -618,7 +566,7 @@ int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { // Do Automatic Save? if (m_bAutoSaveEnabled) { - auto &projectFileManager = ProjectFileManager::Get( *pProject ); + auto &projectFileManager = ProjectFileManager::Get( mProject ); // MY: If this project has already been saved then simply execute a Save here if (m_bProjectAlreadySaved) { bSaveOK = projectFileManager.Save(); @@ -630,9 +578,8 @@ int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { // Do Automatic Export? if (m_bAutoExportEnabled) { Exporter e{ mProject }; - MissingAliasFilesDialog::SetShouldShow(true); bExportOK = e.ProcessFromTimerRecording( - false, 0.0, TrackList::Get( *pProject ).GetEndTime(), + false, 0.0, TrackList::Get( mProject ).GetEndTime(), m_fnAutoExportFile, m_iAutoExportFormat, m_iAutoExportSubFormat, m_iAutoExportFilterIndex); } @@ -716,31 +663,11 @@ int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { if (iDelayOutcome != ProgressResult::Success) { // Cancel the action! iPostRecordAction = POST_TIMER_RECORD_NOTHING; - // Set this to true to avoid any chance of the temp files being deleted - bErrorOverride = true; break; } - - - // If we have simply recorded, exported and then plan to Exit/Restart/Shutdown - // then we will have a temporary project setup. Let's get rid of that! - if (m_bAutoExportEnabled && !m_bAutoSaveEnabled) { - // PRL: Move the following cleanup into a finally? - // No, I think you would want to skip this, in case recording - // succeeded but then save or export threw an exception. - DirManager::CleanTempDir(); - } } while (false); } - // Do we need to cleanup the orphaned temporary project? - if (m_bProjectCleanupRequired && !bErrorOverride) { - // PRL: Move the following cleanup into a finally? - // No, I think you would want to skip this, in case recording - // succeeded but then save or export threw an exception. - RemoveAllAutoSaveFiles(); - } - // Return the action as required return iPostRecordAction; } @@ -917,8 +844,7 @@ void TimerRecordDialog::PopulateOrExchange(ShuttleGui& S) S.StartMultiColumn(3, wxEXPAND); { TranslatableString sInitialValue; - AudacityProject* pProject = &mProject; - auto sSaveValue = pProject->GetFileName(); + auto sSaveValue = ProjectFileIO::Get(mProject).GetFileName(); if (!sSaveValue.empty()) { m_fnAutoSaveFile.Assign(sSaveValue); sInitialValue = XO("Current Project"); diff --git a/src/TimerRecordDialog.h b/src/TimerRecordDialog.h index afe1b47e5..12e973661 100644 --- a/src/TimerRecordDialog.h +++ b/src/TimerRecordDialog.h @@ -116,9 +116,6 @@ private: // Timer Recording Automation Routines void EnableDisableAutoControls(bool bEnable, int iControlGoup); void UpdateTextBoxControls(); - // Tidy up after Timer Recording - bool HaveFilesToRecover(); - bool RemoveAllAutoSaveFiles(); // Add Path Controls to Form TimerRecordPathCtrl *NewPathControl( diff --git a/src/Track.cpp b/src/Track.cpp index 88006b9a2..df95f3031 100644 --- a/src/Track.cpp +++ b/src/Track.cpp @@ -38,7 +38,6 @@ and TimeTrack. #include "tracks/ui/CommonTrackPanelCell.h" #include "Project.h" #include "ProjectSettings.h" -#include "DirManager.h" #include "InconsistencyException.h" @@ -47,9 +46,9 @@ and TimeTrack. #pragma warning( disable : 4786 ) #endif -Track::Track(const std::shared_ptr &projDirManager) +Track::Track(AudacityProject *project) : vrulerSize(36,0), - mDirManager(projDirManager) + mProject(project) { mSelected = false; mLinked = false; @@ -77,7 +76,7 @@ void Track::Init(const Track &orig) mDefaultName = orig.mDefaultName; mName = orig.mName; - mDirManager = orig.mDirManager; + mProject = orig.mProject; mSelected = orig.mSelected; mLinked = orig.mLinked; @@ -1285,11 +1284,10 @@ bool TrackList::HasPendingTracks() const #include "ViewInfo.h" static auto TrackFactoryFactory = []( AudacityProject &project ) { - auto &dirManager = DirManager::Get( project ); auto &viewInfo = ViewInfo::Get( project ); return std::make_shared< TrackFactory >( ProjectSettings::Get( project ), - dirManager.shared_from_this(), &viewInfo ); + project, &viewInfo ); }; static const AudacityProject::AttachedObjects::RegisteredFactory key2{ diff --git a/src/Track.h b/src/Track.h index 047b4751c..77d739d21 100644 --- a/src/Track.h +++ b/src/Track.h @@ -30,7 +30,6 @@ class wxTextFile; class CommonTrackCell; -class DirManager; class Track; class AudioTrack; class PlayableTrack; @@ -310,11 +309,11 @@ private: ChannelType mChannel; double mOffset; - mutable std::shared_ptr mDirManager; + mutable AudacityProject *mProject; public: - Track(const std::shared_ptr &projDirManager); + Track(AudacityProject *project); Track(const Track &orig); virtual ~ Track(); @@ -353,11 +352,11 @@ public: virtual void SetPan( float ){ ;} virtual void SetPanFromChannelType(){ ;}; - // AS: Note that the dirManager is mutable. This is + // AS: Note that the project is mutable. This is // mostly to support "Duplicate" of const objects, // but in general, mucking with the dir manager is // separate from the Track. - const std::shared_ptr &GetDirManager() const { return mDirManager; } + AudacityProject *GetProject() const { return mProject; } // Create a NEW track and modify this track // Return non-NULL or else throw @@ -717,8 +716,8 @@ protected: class AUDACITY_DLL_API AudioTrack /* not final */ : public Track { public: - AudioTrack(const std::shared_ptr &projDirManager) - : Track{ projDirManager } {} + AudioTrack(AudacityProject *project) + : Track{ project } {} AudioTrack(const Track &orig) : Track{ orig } {} // Serialize, not with tags of its own, but as attributes within a tag. @@ -732,8 +731,8 @@ public: class AUDACITY_DLL_API PlayableTrack /* not final */ : public AudioTrack { public: - PlayableTrack(const std::shared_ptr &projDirManager) - : AudioTrack{ projDirManager } {} + PlayableTrack(AudacityProject *project) + : AudioTrack{ project } {} PlayableTrack(const Track &orig) : AudioTrack{ orig } {} bool GetMute () const { return mMute; } @@ -1410,12 +1409,6 @@ public: double GetMinOffset() const; -#if LEGACY_PROJECT_FILE_SUPPORT - // File I/O - bool Load(wxTextFile * in, DirManager * dirManager) override; - bool Save(wxTextFile * out, bool overwrite) override; -#endif - private: // Visit all tracks satisfying a predicate, mutative access @@ -1584,9 +1577,9 @@ class AUDACITY_DLL_API TrackFactory final static void Destroy( AudacityProject &project ); TrackFactory( const ProjectSettings &settings, - const std::shared_ptr &dirManager, const ZoomInfo *zoomInfo) + AudacityProject &project, const ZoomInfo *zoomInfo) : mSettings{ settings } - , mDirManager(dirManager) + , mProject(project) , mZoomInfo(zoomInfo) { } @@ -1595,7 +1588,7 @@ class AUDACITY_DLL_API TrackFactory final private: const ProjectSettings &mSettings; - const std::shared_ptr mDirManager; + AudacityProject &mProject; const ZoomInfo *const mZoomInfo; friend class AudacityProject; public: diff --git a/src/UndoManager.cpp b/src/UndoManager.cpp index 3bd24be17..a7f5e9c01 100644 --- a/src/UndoManager.cpp +++ b/src/UndoManager.cpp @@ -25,10 +25,10 @@ UndoManager #include -#include "BlockFile.h" #include "Clipboard.h" #include "Diags.h" #include "Project.h" +#include "SampleBlock.h" #include "Sequence.h" #include "WaveClip.h" #include "WaveTrack.h" // temp @@ -44,8 +44,8 @@ wxDEFINE_EVENT(EVT_UNDO_MODIFIED, wxCommandEvent); wxDEFINE_EVENT(EVT_UNDO_OR_REDO, wxCommandEvent); wxDEFINE_EVENT(EVT_UNDO_RESET, wxCommandEvent); -using ConstBlockFilePtr = const BlockFile*; -using Set = std::unordered_set; +using ConstSampleBlockPtr = const SampleBlock*; +using Set = std::unordered_set; struct UndoStackElem { @@ -108,19 +108,19 @@ namespace { auto blocks = clip->GetSequenceBlockArray(); for (const auto &block : *blocks) { - const auto &file = block.f; + const auto &sb = block.sb; // Accumulate space used by the file if the file was not // yet seen - if ( !seen || (seen->count( &*file ) == 0 ) ) + if ( !seen || (seen->count( &*sb ) == 0 ) ) { - unsigned long long usage{ file->GetSpaceUsage() }; + unsigned long long usage{ sb->GetSpaceUsage() }; result += usage; } // Add file to current set if (seen) - seen->insert( &*file ); + seen->insert( &*sb ); } } } diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index 888f221d4..b7b77c847 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -119,12 +119,12 @@ static void ComputeSpectrumUsingRealFFTf } } -WaveClip::WaveClip(const std::shared_ptr &projDirManager, +WaveClip::WaveClip(AudacityProject *project, sampleFormat format, int rate, int colourIndex) { mRate = rate; mColourIndex = colourIndex; - mSequence = std::make_unique(projDirManager, format); + mSequence = std::make_unique(project, format); mEnvelope = std::make_unique(true, 1e-7, 2.0, 1.0); @@ -134,17 +134,17 @@ WaveClip::WaveClip(const std::shared_ptr &projDirManager, } WaveClip::WaveClip(const WaveClip& orig, - const std::shared_ptr &projDirManager, + AudacityProject *project, bool copyCutlines) { // essentially a copy constructor - but you must pass in the - // current project's DirManager, because we might be copying + // current project, because we might be copying // from one project to another mOffset = orig.mOffset; mRate = orig.mRate; mColourIndex = orig.mColourIndex; - mSequence = std::make_unique(*orig.mSequence, projDirManager); + mSequence = std::make_unique(*orig.mSequence, project); mEnvelope = std::make_unique(*orig.mEnvelope); @@ -155,13 +155,13 @@ WaveClip::WaveClip(const WaveClip& orig, if ( copyCutlines ) for (const auto &clip: orig.mCutLines) mCutLines.push_back - ( std::make_unique( *clip, projDirManager, true ) ); + ( std::make_unique( *clip, project, true ) ); mIsPlaceholder = orig.GetIsPlaceholder(); } WaveClip::WaveClip(const WaveClip& orig, - const std::shared_ptr &projDirManager, + AudacityProject *project, bool copyCutlines, double t0, double t1) { @@ -199,7 +199,7 @@ WaveClip::WaveClip(const WaveClip& orig, if (cutlinePosition >= t0 && cutlinePosition <= t1) { auto newCutLine = - std::make_unique< WaveClip >( *clip, projDirManager, true ); + std::make_unique< WaveClip >( *clip, project, true ); newCutLine->SetOffset( cutlinePosition - t0 ); mCutLines.push_back(std::move(newCutLine)); } @@ -1203,8 +1203,7 @@ void WaveClip::GetDisplayRect(wxRect* r) } void WaveClip::Append(samplePtr buffer, sampleFormat format, - size_t len, unsigned int stride /* = 1 */, - XMLWriter* blockFileLog /*=NULL*/) + size_t len, unsigned int stride /* = 1 */) // PARTIAL-GUARANTEE in case of exceptions: // Some prefix (maybe none) of the buffer is appended, and no content already // flushed to disk is lost. @@ -1228,8 +1227,7 @@ void WaveClip::Append(samplePtr buffer, sampleFormat format, if (mAppendBufferLen >= blockSize) { // flush some previously appended contents // use STRONG-GUARANTEE - mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize, - blockFileLog); + mSequence->Append(mAppendBuffer.ptr(), seqFormat, blockSize); // use NOFAIL-GUARANTEE for rest of this "if" memmove(mAppendBuffer.ptr(), @@ -1259,17 +1257,6 @@ void WaveClip::Append(samplePtr buffer, sampleFormat format, } } -void WaveClip::AppendBlockFile( const BlockFileFactory &factory, size_t len) -// STRONG-GUARANTEE -{ - // use STRONG-GUARANTEE - mSequence->AppendBlockFile( factory, len ); - - // use NOFAIL-GUARANTEE - UpdateEnvelopeTrackLen(); - MarkChanged(); -} - void WaveClip::Flush() // NOFAIL-GUARANTEE that the clip will be in a flushed state. // PARTIAL-GUARANTEE in case of exceptions: @@ -1351,7 +1338,7 @@ XMLTagHandler *WaveClip::HandleXMLChild(const wxChar *tag) { // Nested wave clips are cut lines mCutLines.push_back( - std::make_unique(mSequence->GetDirManager(), + std::make_unique(mSequence->GetProject(), mSequence->GetSampleFormat(), mRate, 0 /*colourindex*/)); return mCutLines.back().get(); } @@ -1387,7 +1374,7 @@ void WaveClip::Paste(double t0, const WaveClip* other) if (clipNeedsResampling || clipNeedsNewFormat) { newClip = - std::make_unique(*other, mSequence->GetDirManager(), true); + std::make_unique(*other, mSequence->GetProject(), true); if (clipNeedsResampling) // The other clip's rate is different from ours, so resample newClip->Resample(mRate); @@ -1408,7 +1395,7 @@ void WaveClip::Paste(double t0, const WaveClip* other) { newCutlines.push_back( std::make_unique - ( *cutline, mSequence->GetDirManager(), + ( *cutline, mSequence->GetProject(), // Recursively copy cutlines of cutlines. They don't need // their offsets adjusted. true)); @@ -1545,7 +1532,7 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1) const double clip_t1 = std::min( t1, GetEndTime() ); auto newClip = std::make_unique< WaveClip > - (*this, mSequence->GetDirManager(), true, clip_t0, clip_t1); + (*this, mSequence->GetProject(), true, clip_t0, clip_t1); newClip->SetOffset( clip_t0 - mOffset ); @@ -1715,7 +1702,7 @@ void WaveClip::Resample(int rate, ProgressDialog *progress) auto numSamples = mSequence->GetNumSamples(); auto newSequence = - std::make_unique(mSequence->GetDirManager(), mSequence->GetSampleFormat()); + std::make_unique(mSequence->GetProject(), mSequence->GetSampleFormat()); /** * We want to keep going as long as we have something to feed the resampler diff --git a/src/WaveClip.h b/src/WaveClip.h index 640f0b26e..aa31fbef3 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -23,10 +23,10 @@ #include +class AudacityProject; class BlockArray; class BlockFile; using BlockFilePtr = std::shared_ptr; -class DirManager; class Envelope; class ProgressDialog; class Sequence; @@ -172,26 +172,26 @@ public: class AUDACITY_DLL_API WaveClip final : public XMLTagHandler { private: - // It is an error to copy a WaveClip without specifying the DirManager. + // It is an error to copy a WaveClip without specifying the project. WaveClip(const WaveClip&) PROHIBITED; WaveClip& operator= (const WaveClip&) PROHIBITED; public: // typical constructor - WaveClip(const std::shared_ptr &projDirManager, sampleFormat format, + WaveClip(AudacityProject *project, sampleFormat format, int rate, int colourIndex); // essentially a copy constructor - but you must pass in the - // current project's DirManager, because we might be copying + // current project, because we might be copying // from one project to another WaveClip(const WaveClip& orig, - const std::shared_ptr &projDirManager, + AudacityProject *project, bool copyCutlines); // Copy only a range from the given WaveClip WaveClip(const WaveClip& orig, - const std::shared_ptr &projDirManager, + AudacityProject *project, bool copyCutlines, double t0, double t1); @@ -280,15 +280,10 @@ public: /// You must call Flush after the last Append void Append(samplePtr buffer, sampleFormat format, - size_t len, unsigned int stride=1, - XMLWriter* blockFileLog = NULL); + size_t len, unsigned int stride=1); /// Flush must be called after last Append void Flush(); - using BlockFileFactory = - std::function< BlockFilePtr( wxFileNameWrapper, size_t /* len */ ) >; - void AppendBlockFile( const BlockFileFactory &factory, size_t len); - /// This name is consistent with WaveTrack::Clear. It performs a "Cut" /// operation (but without putting the cutted audio to the clipboard) void Clear(double t0, double t1); diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index e7c9a334b..37f6440f6 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -91,11 +91,11 @@ WaveTrack::Holder TrackFactory::NewWaveTrack(sampleFormat format, double rate) format = QualityPrefs::SampleFormatChoice(); if (rate == 0) rate = mSettings.GetRate(); - return std::make_shared ( mDirManager, format, rate ); + return std::make_shared ( &mProject, format, rate ); } -WaveTrack::WaveTrack(const std::shared_ptr &projDirManager, sampleFormat format, double rate) : - PlayableTrack(projDirManager) +WaveTrack::WaveTrack(AudacityProject *project, sampleFormat format, double rate) : + PlayableTrack(project) { mLegacyProjectFileOffset = 0; @@ -113,7 +113,6 @@ WaveTrack::WaveTrack(const std::shared_ptr &projDirManager, sampleFo mSpectrumMin = mSpectrumMax = -1; // so values will default to settings mLastScaleType = -1; mLastdBRange = -1; - mAutoSaveIdent = 0; } WaveTrack::WaveTrack(const WaveTrack &orig): @@ -136,7 +135,7 @@ WaveTrack::WaveTrack(const WaveTrack &orig): for (const auto &clip : orig.mClips) mClips.push_back - ( std::make_unique( *clip, mDirManager, true ) ); + ( std::make_unique( *clip, mProject, true ) ); } // Copy the track metadata but not the contents. @@ -531,11 +530,11 @@ void WaveTrack::Trim (double t0, double t1) WaveTrack::Holder WaveTrack::EmptyCopy( - const std::shared_ptr &pDirManager ) const + AudacityProject *pProject ) const { - auto result = std::make_shared( mDirManager, mFormat, mRate ); + auto result = std::make_shared( mProject, mFormat, mRate ); result->Init(*this); - result->mDirManager = pDirManager ? pDirManager : mDirManager; + result->mProject = pProject ? pProject : mProject; return result; } @@ -559,7 +558,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const //wxPrintf("copy: clip %i is in copy region\n", (int)clip); newTrack->mClips.push_back - (std::make_unique(*clip, mDirManager, ! forClipboard)); + (std::make_unique(*clip, mProject, ! forClipboard)); WaveClip *const newClip = newTrack->mClips.back().get(); newClip->Offset(-t0); } @@ -572,7 +571,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const const double clip_t1 = std::min(t1, clip->GetEndTime()); auto newClip = std::make_unique - (*clip, mDirManager, ! forClipboard, clip_t0, clip_t1); + (*clip, mProject, ! forClipboard, clip_t0, clip_t1); //wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1); @@ -591,7 +590,7 @@ Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const if (forClipboard && newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0) { - auto placeholder = std::make_unique(mDirManager, + auto placeholder = std::make_unique(mProject, newTrack->GetSampleFormat(), static_cast(newTrack->GetRate()), 0 /*colourindex*/); @@ -983,7 +982,7 @@ void WaveTrack::HandleClear(double t0, double t1, // Don't modify this clip in place, because we want a strong // guarantee, and might modify another clip clipsToDelete.push_back( clip.get() ); - auto newClip = std::make_unique( *clip, mDirManager, true ); + auto newClip = std::make_unique( *clip, mProject, true ); newClip->ClearAndAddCutLine( t0, t1 ); clipsToAdd.push_back( std::move( newClip ) ); } @@ -998,7 +997,7 @@ void WaveTrack::HandleClear(double t0, double t1, // Don't modify this clip in place, because we want a strong // guarantee, and might modify another clip clipsToDelete.push_back( clip.get() ); - auto newClip = std::make_unique( *clip, mDirManager, true ); + auto newClip = std::make_unique( *clip, mProject, true ); newClip->Clear(clip->GetStartTime(), t1); newClip->Offset(t1-clip->GetStartTime()); @@ -1010,7 +1009,7 @@ void WaveTrack::HandleClear(double t0, double t1, // Don't modify this clip in place, because we want a strong // guarantee, and might modify another clip clipsToDelete.push_back( clip.get() ); - auto newClip = std::make_unique( *clip, mDirManager, true ); + auto newClip = std::make_unique( *clip, mProject, true ); newClip->Clear(t0, clip->GetEndTime()); clipsToAdd.push_back( std::move( newClip ) ); @@ -1021,12 +1020,12 @@ void WaveTrack::HandleClear(double t0, double t1, // left clipsToAdd.push_back - ( std::make_unique( *clip, mDirManager, true ) ); + ( std::make_unique( *clip, mProject, true ) ); clipsToAdd.back()->Clear(t0, clip->GetEndTime()); // right clipsToAdd.push_back - ( std::make_unique( *clip, mDirManager, true ) ); + ( std::make_unique( *clip, mProject, true ) ); WaveClip *const right = clipsToAdd.back().get(); right->Clear(clip->GetStartTime(), t1); right->Offset(t1 - clip->GetStartTime()); @@ -1040,7 +1039,7 @@ void WaveTrack::HandleClear(double t0, double t1, // Don't modify this clip in place, because we want a strong // guarantee, and might modify another clip clipsToDelete.push_back( clip.get() ); - auto newClip = std::make_unique( *clip, mDirManager, true ); + auto newClip = std::make_unique( *clip, mProject, true ); // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion newClip->Clear(t0,t1); @@ -1106,7 +1105,7 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1) // AWD: Could just use InsertSilence() on its own here, but it doesn't // follow EditClipCanMove rules (Paste() does it right) auto tmp = std::make_shared( - mDirManager, GetSampleFormat(), GetRate() ); + mProject, GetSampleFormat(), GetRate() ); tmp->InsertSilence(0.0, newT1 - oldT1); tmp->Flush(); @@ -1261,7 +1260,7 @@ void WaveTrack::Paste(double t0, const Track *src) if (!clip->GetIsPlaceholder()) { auto newClip = - std::make_unique( *clip, mDirManager, true ); + std::make_unique( *clip, mProject, true ); newClip->Resample(mRate); newClip->Offset(t0); newClip->MarkChanged(); @@ -1323,7 +1322,7 @@ void WaveTrack::InsertSilence(double t, double len) if (mClips.empty()) { // Special case if there is no clip yet - auto clip = std::make_unique(mDirManager, mFormat, mRate, this->GetWaveColorIndex()); + auto clip = std::make_unique(mProject, mFormat, mRate, this->GetWaveColorIndex()); clip->InsertSilence(0, len); // use NOFAIL-GUARANTEE mClips.push_back( std::move( clip ) ); @@ -1483,14 +1482,12 @@ void WaveTrack::Join(double t0, double t1) } void WaveTrack::Append(samplePtr buffer, sampleFormat format, - size_t len, unsigned int stride /* = 1 */, - XMLWriter *blockFileLog /* = NULL */) + size_t len, unsigned int stride /* = 1 */) // PARTIAL-GUARANTEE in case of exceptions: // Some prefix (maybe none) of the buffer is appended, and no content already // flushed to disk is lost. { - RightmostOrNewClip()->Append(buffer, format, len, stride, - blockFileLog); + RightmostOrNewClip()->Append(buffer, format, len, stride); } sampleCount WaveTrack::GetBlockStart(sampleCount s) const @@ -1536,7 +1533,7 @@ size_t WaveTrack::GetMaxBlockSize() const { // We really need the maximum block size, so create a // temporary sequence to get it. - maxblocksize = Sequence{ mDirManager, mFormat }.GetMaxBlockSize(); + maxblocksize = Sequence{ mProject, mFormat }.GetMaxBlockSize(); } wxASSERT(maxblocksize > 0); @@ -1613,9 +1610,6 @@ 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; else if (!wxStrcmp(attr, wxT("colorindex")) && XMLValueChecker::IsGoodString(strValue) && strValue.ToLong(&nValue)) @@ -1675,10 +1669,6 @@ void WaveTrack::WriteXML(XMLWriter &xmlFile) const // may throw { xmlFile.StartTag(wxT("wavetrack")); - if (mAutoSaveIdent) - { - xmlFile.WriteAttr(wxT("autosaveid"), mAutoSaveIdent); - } this->Track::WriteCommonXMLAttributes( xmlFile ); xmlFile.WriteAttr(wxT("channel"), mChannel); xmlFile.WriteAttr(wxT("linked"), mLinked); @@ -2119,7 +2109,7 @@ Sequence* WaveTrack::GetSequenceAtX(int xcoord) WaveClip* WaveTrack::CreateClip() { - mClips.push_back(std::make_unique(mDirManager, mFormat, mRate, GetWaveColorIndex())); + mClips.push_back(std::make_unique(mProject, mFormat, mRate, GetWaveColorIndex())); return mClips.back().get(); } @@ -2279,7 +2269,7 @@ void WaveTrack::SplitAt(double t) if (c->WithinClip(t)) { t = LongSamplesToTime(TimeToLongSamples(t)); // put t on a sample - auto newClip = std::make_unique( *c, mDirManager, true ); + auto newClip = std::make_unique( *c, mProject, true ); c->Clear(t, c->GetEndTime()); newClip->Clear(c->GetStartTime(), t); @@ -2481,16 +2471,6 @@ void WaveTrack::ClearWaveCaches() clip->ClearWaveCache(); } -int WaveTrack::GetAutoSaveIdent() const -{ - return mAutoSaveIdent; -} - -void WaveTrack::SetAutoSaveIdent(int ident) -{ - mAutoSaveIdent = ident; -} - WaveTrackCache::~WaveTrackCache() { } diff --git a/src/WaveTrack.h b/src/WaveTrack.h index 8ed4c0ac1..a105c69a3 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -68,8 +68,7 @@ public: // Constructor / Destructor / Duplicator // - WaveTrack(const std::shared_ptr &projDirManager, - sampleFormat format, double rate); + WaveTrack(AudacityProject *project, sampleFormat format, double rate); WaveTrack(const WaveTrack &orig); // overwrite data excluding the sample sequence but including display @@ -160,12 +159,11 @@ private: // Make another track copying format, rate, color, etc. but containing no // clips - // It is important to pass the correct DirManager (that for the project + // It is important to pass the correct project (that for the project // which will own the copy) in the unusual case that a track is copied from // another project or the clipboard. For copies within one project, the // default will do. - Holder EmptyCopy( - const std::shared_ptr &pDirManager = {} ) const; + Holder EmptyCopy(AudacityProject *pProject = {} ) const; // If forClipboard is true, // and there is no clip at the end time of the selection, then the result @@ -222,8 +220,7 @@ private: * one is created. */ void Append(samplePtr buffer, sampleFormat format, - size_t len, unsigned int stride=1, - XMLWriter* blockFileLog=NULL); + size_t len, unsigned int stride=1); /// Flush must be called after last Append void Flush(); @@ -524,14 +521,6 @@ private: // Resample track (i.e. all clips in the track) void Resample(int rate, ProgressDialog *progress = NULL); - // - // AutoSave related - // - // Retrieve the unique autosave ID - int GetAutoSaveIdent() const; - // Set the unique autosave ID - void SetAutoSaveIdent(int id); - int GetLastScaleType() const { return mLastScaleType; } void SetLastScaleType() const; @@ -591,7 +580,6 @@ private: wxCriticalSection mFlushCriticalSection; wxCriticalSection mAppendCriticalSection; double mLegacyProjectFileOffset; - int mAutoSaveIdent; std::unique_ptr mpSpectrumSettings; std::unique_ptr mpWaveformSettings; diff --git a/src/commands/OpenSaveCommands.cpp b/src/commands/OpenSaveCommands.cpp index 34cf596e9..39ff35bd8 100644 --- a/src/commands/OpenSaveCommands.cpp +++ b/src/commands/OpenSaveCommands.cpp @@ -19,6 +19,7 @@ #include "LoadCommands.h" #include "../Project.h" +#include "../ProjectFileIO.h" #include "../ProjectFileManager.h" #include "../ProjectManager.h" #include "../export/Export.h" @@ -52,7 +53,9 @@ void OpenProjectCommand::PopulateOrExchange(ShuttleGui & S) bool OpenProjectCommand::Apply(const CommandContext & context){ - auto oldFileName = context.project.GetFileName(); + auto &projectFileIO = ProjectFileIO::Get(context.project); + + auto oldFileName = projectFileIO.GetFileName(); if(mFileName.empty()) { auto project = &context.project; @@ -63,7 +66,7 @@ bool OpenProjectCommand::Apply(const CommandContext & context){ ProjectFileManager::Get( context.project ) .OpenFile(mFileName, mbAddToHistory); } - const auto &newFileName = context.project.GetFileName(); + const auto &newFileName = projectFileIO.GetFileName(); // Because Open does not return a success or failure, we have to guess // at this point, based on whether the project file name has @@ -79,7 +82,6 @@ namespace{ BuiltinCommandsModule::Registration< SaveProjectCommand > reg2; } bool SaveProjectCommand::DefineParams( ShuttleParams & S ){ S.Define( mFileName, wxT("Filename"), "name.aup" ); S.Define( mbAddToHistory, wxT("AddToHistory"), false ); - S.Define( mbCompress, wxT("Compress"), false ); return true; } @@ -91,7 +93,6 @@ void SaveProjectCommand::PopulateOrExchange(ShuttleGui & S) { S.TieTextBox(XXO("File Name:"),mFileName); S.TieCheckBox(XXO("Add to History"), mbAddToHistory ); - S.TieCheckBox(XXO("Compress"), mbCompress ); } S.EndMultiColumn(); } @@ -100,8 +101,7 @@ bool SaveProjectCommand::Apply(const CommandContext &context) { auto &projectFileManager = ProjectFileManager::Get( context.project ); if ( mFileName.empty() ) - return projectFileManager.SaveAs(mbCompress); + return projectFileManager.SaveAs(); else - return projectFileManager.SaveAs( - mFileName, mbCompress, mbAddToHistory); + return projectFileManager.SaveAs(mFileName, mbAddToHistory); } diff --git a/src/commands/OpenSaveCommands.h b/src/commands/OpenSaveCommands.h index 13ac8fd58..b10c92623 100644 --- a/src/commands/OpenSaveCommands.h +++ b/src/commands/OpenSaveCommands.h @@ -58,7 +58,5 @@ public: public: wxString mFileName; bool mbAddToHistory; - bool mbCompress; bool bHasAddToHistory; - bool bHasCompress; }; diff --git a/src/effects/Contrast.cpp b/src/effects/Contrast.cpp index e77e84b2a..b2d9390b9 100644 --- a/src/effects/Contrast.cpp +++ b/src/effects/Contrast.cpp @@ -16,6 +16,7 @@ #include "../WaveTrack.h" #include "../Prefs.h" #include "../Project.h" +#include "../ProjectFileIO.h" #include "../ProjectSettings.h" #include "../ProjectWindow.h" #include "../ShuttleGui.h" @@ -557,7 +558,7 @@ void ContrastDialog::OnExport(wxCommandEvent & WXUNUSED(event)) /* i18n-hint: WCAG abbreviates Web Content Accessibility Guidelines */ << XO("WCAG 2.0 Success Criteria 1.4.7 Contrast Results") << '\n' << '\n' - << XO("Filename = %s.").Format( project->GetFileName() ) << '\n' + << XO("Filename = %s.").Format( ProjectFileIO::Get(*project).GetFileName() ) << '\n' << '\n' << XO("Foreground") << '\n'; diff --git a/src/effects/EffectUI.cpp b/src/effects/EffectUI.cpp index 1d7d02e4b..5dcaac090 100644 --- a/src/effects/EffectUI.cpp +++ b/src/effects/EffectUI.cpp @@ -1842,7 +1842,6 @@ wxDialog *EffectUI::DialogFactory( wxWindow &parent, EffectHostInterface *pHost, return nullptr; }; -#include "../MissingAliasFileDialog.h" #include "../PluginManager.h" #include "../ProjectSettings.h" #include "../ProjectWindow.h" @@ -1885,8 +1884,6 @@ wxDialog *EffectUI::DialogFactory( wxWindow &parent, EffectHostInterface *pHost, SelectUtilities::SelectAllIfNone( project ); } - MissingAliasFilesDialog::SetShouldShow(true); - auto nTracksOriginally = tracks.size(); wxWindow *focus = wxWindow::FindFocus(); wxWindow *parent = nullptr; diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index 9c3246721..657f89586 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -54,7 +54,6 @@ effects from this one class. #include #include "../EffectManager.h" -#include "../../DirManager.h" #include "../../FileNames.h" #include "../../LabelTrack.h" #include "../../NoteTrack.h" diff --git a/src/export/Export.cpp b/src/export/Export.cpp index da8caa480..ff35fad43 100644 --- a/src/export/Export.cpp +++ b/src/export/Export.cpp @@ -50,7 +50,6 @@ #include "../widgets/FileDialog/FileDialog.h" -#include "../DirManager.h" #include "../FileFormats.h" #include "../Mix.h" #include "../Prefs.h" @@ -745,31 +744,6 @@ bool Exporter::GetFilename() continue; } - // Check to see if we are writing to a path that a missing aliased file existed at. - // This causes problems for the exporter, so we don't allow it. - // Overwritting non-missing aliased files is okay. - // Also, this can only happen for uncompressed audio. - bool overwritingMissingAliasFiles; - overwritingMissingAliasFiles = false; - for (auto pProject : AllProjects{}) { - AliasedFileArray aliasedFiles; - FindDependencies(pProject.get(), aliasedFiles); - for (const auto &aliasedFile : aliasedFiles) { - if (mFilename.GetFullPath() == aliasedFile.mFileName.GetFullPath() && - !mFilename.FileExists()) { - // Warn and return to the dialog - AudacityMessageBox(XO( -"You are attempting to overwrite an aliased file that is missing.\n\ -The file cannot be written because the path is needed to restore the original audio to the project.\n\ -Choose Help > Diagnostics > Check Dependencies to view the locations of all missing files.\n\ -If you still wish to export, please choose a different filename or folder.")); - overwritingMissingAliasFiles = true; - } - } - } - if (overwritingMissingAliasFiles) - continue; - // For Mac, it's handled by the FileDialog #if !defined(__WXMAC__) if (mFilename.FileExists()) { @@ -800,16 +774,6 @@ If you still wish to export, please choose a different filename or folder.")); // bool Exporter::CheckFilename() { - // - // Ensure that exporting a file by this name doesn't overwrite - // one of the existing files in the project. (If it would - // overwrite an existing file, DirManager tries to rename the - // existing file.) - // - - if (!DirManager::Get( *mProject ).EnsureSafeFilename(mFilename)) - return false; - if( mFormatName.empty() ) gPrefs->Write(wxT("/Export/Format"), mPlugins[mFormat]->GetFormat(mSubFormat)); gPrefs->Write(wxT("/Export/Path"), mFilename.GetPath()); diff --git a/src/export/Export.h b/src/export/Export.h index 63d07de59..e488adf6c 100644 --- a/src/export/Export.h +++ b/src/export/Export.h @@ -28,7 +28,6 @@ class wxMemoryDC; class wxSimplebook; class wxStaticText; class AudacityProject; -class DirManager; class WaveTrack; class Tags; class TrackList; diff --git a/src/export/ExportMultiple.cpp b/src/export/ExportMultiple.cpp index 84ab9ea94..2bf227ada 100644 --- a/src/export/ExportMultiple.cpp +++ b/src/export/ExportMultiple.cpp @@ -37,7 +37,6 @@ #include #include -#include "../DirManager.h" #include "../FileFormats.h" #include "../FileNames.h" #include "../LabelTrack.h" @@ -1062,10 +1061,6 @@ ProgressResult ExportMultipleDialog::DoExport(std::unique_ptr &p wxFileName backup; if (mOverwrite->GetValue()) { - // Make sure we don't overwrite (corrupt) alias files - if (!DirManager::Get( *mProject ).EnsureSafeFilename(inName)) { - return ProgressResult::Cancelled; - } name = inName; backup.Assign(name); diff --git a/src/export/ExportPCM.cpp b/src/export/ExportPCM.cpp index 3836443ae..a24a6129a 100644 --- a/src/export/ExportPCM.cpp +++ b/src/export/ExportPCM.cpp @@ -90,24 +90,10 @@ static void SaveOtherFormat(int val) static int LoadEncoding(int type) { - // LLL: Temporary hack until I can figure out how to add an "ExportPCMCommand" - // to create a 32-bit float WAV file. It tells the ExportPCM exporter - // to use float when exporting the next WAV file. - // - // This was done as part of the resolution for bug #2062. - // - // See: ProjectFileManager.cpp, ProjectFileManager::SaveCopyWaveTracks() - if (gPrefs->HasEntry(wxT("/FileFormats/ExportFormat_SF1_ForceFloat"))) - { - gPrefs->DeleteEntry(wxT("/FileFormats/ExportFormat_SF1_ForceFloat")); - gPrefs->Flush(); - - return SF_FORMAT_FLOAT; - } - return gPrefs->Read(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"), sf_header_shortname(type), type), (long int) 0); } + static void SaveEncoding(int type, int val) { gPrefs->Write(wxString::Format(wxT("/FileFormats/ExportFormat_SF1_Type/%s_%x"), diff --git a/src/import/Import.cpp b/src/import/Import.cpp index f32e74962..a2a23c315 100644 --- a/src/import/Import.cpp +++ b/src/import/Import.cpp @@ -785,7 +785,7 @@ bool Importer::Import( AudacityProject &project, } // Audacity project - if (extension.IsSameAs(wxT("aup"), false)) { + if (extension.IsSameAs(wxT("aup3"), false)) { errorMessage = XO( /* i18n-hint: %s will be the filename */ "\"%s\" is an Audacity Project file. \nUse the 'File > Open' command to open Audacity Projects.") diff --git a/src/import/ImportRaw.cpp b/src/import/ImportRaw.cpp index 45127bc14..2e8fa45e4 100644 --- a/src/import/ImportRaw.cpp +++ b/src/import/ImportRaw.cpp @@ -24,7 +24,6 @@ and sample size to help you importing data of an unknown format. #include "../Audacity.h" #include "ImportRaw.h" -#include "../DirManager.h" #include "../FileFormats.h" #include "../Prefs.h" #include "../ShuttleGui.h" diff --git a/src/import/ImportRaw.h b/src/import/ImportRaw.h index b48ab7866..c0a7f6fb4 100644 --- a/src/import/ImportRaw.h +++ b/src/import/ImportRaw.h @@ -15,7 +15,6 @@ class TrackFactory; class WaveTrack; -class DirManager; class wxString; class wxWindow; diff --git a/src/menus/EditMenus.cpp b/src/menus/EditMenus.cpp index b93f4a292..3c96ef36a 100644 --- a/src/menus/EditMenus.cpp +++ b/src/menus/EditMenus.cpp @@ -2,7 +2,6 @@ #include "../AdornedRulerPanel.h" #include "../Clipboard.h" #include "../CommonCommandFlags.h" -#include "../DirManager.h" #include "../LabelTrack.h" #include "../Menus.h" #include "../NoteTrack.h" @@ -76,7 +75,6 @@ bool DoPasteText(AudacityProject &project) bool DoPasteNothingSelected(AudacityProject &project) { auto &tracks = TrackList::Get( project ); - auto &dirManager = DirManager::Get( project ); auto &trackFactory = TrackFactory::Get( project ); auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; auto &window = ProjectWindow::Get( project ); @@ -103,7 +101,7 @@ bool DoPasteNothingSelected(AudacityProject &project) // Cause duplication of block files on disk, when copy is // between projects locker.emplace(wc); - uNewTrack = wc->EmptyCopy( dirManager.shared_from_this() ); + uNewTrack = wc->EmptyCopy(); pNewTrack = uNewTrack.get(); }, #ifdef USE_MIDI @@ -381,7 +379,6 @@ void OnPaste(const CommandContext &context) { auto &project = context.project; auto &tracks = TrackList::Get( project ); - auto &dirManager = DirManager::Get( project ); auto &trackFactory = TrackFactory::Get( project ); auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; const auto &settings = ProjectSettings::Get( project ); @@ -599,7 +596,7 @@ void OnPaste(const CommandContext &context) wt->ClearAndPaste(t0, t1, wc, true, true); } else { - auto tmp = wt->EmptyCopy( dirManager.shared_from_this() ); + auto tmp = wt->EmptyCopy(); tmp->InsertSilence( 0.0, // MJS: Is this correct? clipboard.Duration() ); diff --git a/src/menus/FileMenus.cpp b/src/menus/FileMenus.cpp index 68da22b7a..5c2c89c63 100644 --- a/src/menus/FileMenus.cpp +++ b/src/menus/FileMenus.cpp @@ -5,11 +5,11 @@ #include "../CommonCommandFlags.h" #include "../FileNames.h" #include "../LabelTrack.h" -#include "../MissingAliasFileDialog.h" #include "../NoteTrack.h" #include "../Prefs.h" #include "../Printing.h" #include "../Project.h" +#include "../ProjectFileIO.h" #include "../ProjectFileManager.h" #include "../ProjectHistory.h" #include "../ProjectManager.h" @@ -38,16 +38,16 @@ namespace { void DoExport( AudacityProject &project, const FileExtension & Format ) { auto &tracks = TrackList::Get( project ); - + auto &projectFileIO = ProjectFileIO::Get( project ); + Exporter e{ project }; - MissingAliasFilesDialog::SetShouldShow(true); double t0 = 0.0; double t1 = tracks.GetEndTime(); // Prompt for file name and/or extension? bool bPromptingRequired = - (project.mBatchMode == 0) || project.GetFileName().empty() || + (project.mBatchMode == 0) || projectFileIO.GetFileName().empty() || Format.empty(); wxString filename; @@ -58,7 +58,7 @@ void DoExport( AudacityProject &project, const FileExtension & Format ) extension.MakeLower(); filename = - MacroCommands::BuildCleanFileName(project.GetFileName(), extension); + MacroCommands::BuildCleanFileName(projectFileIO.GetFileName(), extension); // Bug 1854, No warning of file overwrite // (when export is called from Macros). @@ -72,7 +72,7 @@ void DoExport( AudacityProject &project, const FileExtension & Format ) number.Printf("%03i", counter); // So now the name has a number in it too. filename = MacroCommands::BuildCleanFileName( - project.GetFileName() + number, extension); + projectFileIO.GetFileName() + number, extension); bPromptingRequired = wxFileExists(filename); } // If we've run out of alternative names, we will fall back to prompting @@ -155,22 +155,6 @@ void OnSaveAs(const CommandContext &context ) projectFileManager.SaveAs(); } -void OnSaveCopy(const CommandContext &context ) -{ - auto &project = context.project; - auto &projectFileManager = ProjectFileManager::Get( project ); - projectFileManager.SaveAs(true, true); -} - -#ifdef USE_LIBVORBIS -void OnSaveCompressed(const CommandContext &context) -{ - auto &project = context.project; - auto &projectFileManager = ProjectFileManager::Get( project ); - projectFileManager.SaveAs(true); -} -#endif - void OnExportMp3(const CommandContext &context) { auto &project = context.project; @@ -201,7 +185,6 @@ void OnExportSelection(const CommandContext &context) auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; Exporter e{ project }; - MissingAliasFilesDialog::SetShouldShow(true); e.SetFileDialogTitle( XO("Export Selected Audio") ); e.Process(true, selectedRegion.t0(), selectedRegion.t1()); @@ -274,7 +257,6 @@ void OnExportMultiple(const CommandContext &context) auto &project = context.project; ExportMultipleDialog em(&project); - MissingAliasFilesDialog::SetShouldShow(true); em.ShowModal(); } @@ -371,10 +353,6 @@ void OnImport(const CommandContext &context) auto &project = context.project; auto &window = ProjectWindow::Get( project ); - // An import trigger for the alias missing dialog might not be intuitive, but - // this serves to track the file if the users zooms in and such. - MissingAliasFilesDialog::SetShouldShow(true); - auto selectedFiles = ProjectFileManager::ShowOpenDialog(); if (selectedFiles.size() == 0) { Importer::SetLastOpenType({}); @@ -619,16 +597,7 @@ BaseItemSharedPtr FileMenu() Command( wxT("Save"), XXO("&Save Project"), FN(OnSave), AudioIONotBusyFlag() | UnsavedChangesFlag(), wxT("Ctrl+S") ), Command( wxT("SaveAs"), XXO("Save Project &As..."), FN(OnSaveAs), - AudioIONotBusyFlag() ), - // TODO: The next two items should be disabled if project is empty - Command( wxT("SaveCopy"), XXO("Save Lossless Copy of Project..."), - FN(OnSaveCopy), AudioIONotBusyFlag() ) - #ifdef USE_LIBVORBIS - , - Command( wxT("SaveCompressed"), - XXO("&Save Compressed Copy of Project..."), - FN(OnSaveCompressed), AudioIONotBusyFlag() ) - #endif + AudioIONotBusyFlag() ) ) ), diff --git a/src/menus/HelpMenus.cpp b/src/menus/HelpMenus.cpp index f801bef55..0591ba36c 100644 --- a/src/menus/HelpMenus.cpp +++ b/src/menus/HelpMenus.cpp @@ -367,12 +367,6 @@ void OnCrashReport(const CommandContext &WXUNUSED(context) ) } #endif -void OnCheckDependencies(const CommandContext &context) -{ - auto &project = context.project; - ::ShowDependencyDialogIfNeeded(&project, false); -} - void OnMenuTree(const CommandContext &context) { auto &project = context.project; @@ -533,11 +527,8 @@ BaseItemSharedPtr HelpMenu() AlwaysEnabledFlag ), #if defined(EXPERIMENTAL_CRASH_REPORT) Command( wxT("CrashReport"), XXO("&Generate Support Data..."), - FN(OnCrashReport), AlwaysEnabledFlag ), + FN(OnCrashReport), AlwaysEnabledFlag ) #endif - Command( wxT("CheckDeps"), XXO("Chec&k Dependencies..."), - FN(OnCheckDependencies), - AudioIONotBusyFlag() ) #ifdef IS_ALPHA , diff --git a/src/menus/PluginMenus.cpp b/src/menus/PluginMenus.cpp index a9b1b0531..a06a27d87 100644 --- a/src/menus/PluginMenus.cpp +++ b/src/menus/PluginMenus.cpp @@ -484,9 +484,8 @@ void OnScreenshot(const CommandContext &context ) void OnBenchmark(const CommandContext &context) { auto &project = context.project; - auto &settings = ProjectSettings::Get( project ); auto &window = GetProjectFrame( project ); - ::RunBenchmark( &window, settings ); + ::RunBenchmark( &window, project); } void OnSimulateRecordingErrors(const CommandContext &context) diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp index c555935fb..a699db390 100644 --- a/src/menus/TrackMenus.cpp +++ b/src/menus/TrackMenus.cpp @@ -4,7 +4,6 @@ #include "../CommonCommandFlags.h" #include "../LabelTrack.h" #include "../Menus.h" -#include "../MissingAliasFileDialog.h" #include "../Mix.h" #include "../Prefs.h" @@ -58,8 +57,6 @@ void DoMixAndRender auto &trackPanel = TrackPanel::Get( project ); auto &window = ProjectWindow::Get( project ); - MissingAliasFilesDialog::SetShouldShow(true); - WaveTrack::Holder uNewLeft, uNewRight; ::MixAndRender( &tracks, &trackFactory, rate, defaultFormat, 0.0, 0.0, uNewLeft, uNewRight); diff --git a/src/menus/TransportMenus.cpp b/src/menus/TransportMenus.cpp index 5f3a18159..5517757c3 100644 --- a/src/menus/TransportMenus.cpp +++ b/src/menus/TransportMenus.cpp @@ -305,7 +305,7 @@ void OnTimerRecord(const CommandContext &context) // We use this variable to display "Current Project" in the Timer Recording // save project field - bool bProjectSaved = ProjectFileIO::Get( project ).IsProjectSaved(); + bool bProjectSaved = !ProjectFileIO::Get( project ).IsModified(); //we break the prompting and waiting dialogs into two sections //because they both give the user a chance to click cancel diff --git a/src/xml/XMLFileReader.cpp b/src/xml/XMLFileReader.cpp index e5a951a9d..5272721cc 100644 --- a/src/xml/XMLFileReader.cpp +++ b/src/xml/XMLFileReader.cpp @@ -132,6 +132,45 @@ bool XMLFileReader::Parse(XMLTagHandler *baseHandler, } } +bool XMLFileReader::ParseString(XMLTagHandler *baseHandler, + const wxString &xmldata) +{ + const char *buffer = xmldata.mb_str().data(); + int len = xmldata.mbc_str().length(); + + mBaseHandler = baseHandler; + + if (!XML_Parse(mParser, buffer, len, true)) + { + + // Embedded error string from expat doesn't translate (yet) + // We could make a table of XOs if we wanted so that it could + // If we do, uncomment the second constructor argument so it's not + // a verbatim string + mLibraryErrorStr = Verbatim( + XML_ErrorString(XML_GetErrorCode(mParser)) // , {} + ); + + mErrorStr = XO("Error: %s at line %lu").Format( + mLibraryErrorStr, + (long unsigned int)XML_GetCurrentLineNumber(mParser) + ); + + return false; + } + + // Even though there were no parse errors, we only succeed if + // the first-level handler actually got called, and didn't + // return false. + if (!mBaseHandler) + { + mErrorStr = XO("Could not XML"); + return false; + } + + return true; +} + const TranslatableString &XMLFileReader::GetErrorStr() const { return mErrorStr; diff --git a/src/xml/XMLFileReader.h b/src/xml/XMLFileReader.h index 618c1c1a5..258adb2b0 100644 --- a/src/xml/XMLFileReader.h +++ b/src/xml/XMLFileReader.h @@ -24,6 +24,8 @@ class AUDACITY_DLL_API XMLFileReader final { bool Parse(XMLTagHandler *baseHandler, const FilePath &fname); + bool ParseString(XMLTagHandler *baseHandler, + const wxString &xmldata); const TranslatableString &GetErrorStr() const; const TranslatableString &GetLibraryErrorStr() const; diff --git a/src/xml/XMLWriter.h b/src/xml/XMLWriter.h index bb0771fa3..28826a9c6 100644 --- a/src/xml/XMLWriter.h +++ b/src/xml/XMLWriter.h @@ -142,8 +142,6 @@ class XMLStringWriter final : public wxString, public XMLWriter { void Write(const wxString &data) override; - wxString Get(); - private: };