2019-06-09 14:25:01 +00:00
/**********************************************************************
Audacity : A Digital Audio Editor
ProjectFileManager . cpp
Paul Licameli split from AudacityProject . cpp
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "ProjectFileManager.h"
# include "Experimental.h"
# include <wx/crt.h> // for wxPrintf
2019-06-08 20:05:22 +00:00
# if defined(__WXGTK__)
# include <wx/evtloop.h>
# endif
2019-06-09 14:25:01 +00:00
# include <wx/frame.h>
# include "AutoRecovery.h"
# include "Dependencies.h"
# include "DirManager.h"
2019-04-02 22:40:42 +00:00
# include "FileFormats.h"
2019-06-09 14:25:01 +00:00
# include "FileNames.h"
2019-06-08 20:05:22 +00:00
# include "Legacy.h"
# include "PlatformCompatibility.h"
2019-06-09 14:25:01 +00:00
# include "Project.h"
# include "ProjectFileIO.h"
2019-06-08 20:05:22 +00:00
# include "ProjectFileIORegistry.h"
# include "ProjectFSCK.h"
# include "ProjectHistory.h"
# include "ProjectSelectionManager.h"
2019-06-09 14:25:01 +00:00
# include "ProjectSettings.h"
2019-07-02 15:38:15 +00:00
# include "ProjectStatus.h"
2019-06-08 20:05:22 +00:00
# include "ProjectWindow.h"
2019-06-21 23:38:38 +00:00
# include "SelectUtilities.h"
2019-06-09 14:25:01 +00:00
# include "SelectionState.h"
2019-06-08 20:05:22 +00:00
# include "Sequence.h"
# include "Tags.h"
2019-07-01 22:32:18 +00:00
# include "TrackPanelAx.h"
2019-06-08 20:05:22 +00:00
# include "TrackPanel.h"
2019-06-09 14:25:01 +00:00
# include "UndoManager.h"
2019-06-08 20:05:22 +00:00
# include "WaveClip.h"
2019-06-09 14:25:01 +00:00
# include "WaveTrack.h"
# include "wxFileNameWrapper.h"
2019-06-05 15:54:13 +00:00
# include "blockfile/ODDecodeBlockFile.h"
2019-06-09 14:25:01 +00:00
# include "export/Export.h"
2019-06-08 20:05:22 +00:00
# include "import/Import.h"
2019-06-25 02:31:11 +00:00
# include "import/ImportMIDI.h"
2019-06-08 20:05:22 +00:00
# include "commands/CommandContext.h"
2019-06-09 14:25:01 +00:00
# include "ondemand/ODComputeSummaryTask.h"
2019-06-23 01:40:36 +00:00
# include "ondemand/ODDecodeFlacTask.h"
2019-06-09 14:25:01 +00:00
# include "ondemand/ODManager.h"
# include "ondemand/ODTask.h"
2019-06-08 20:05:22 +00:00
# include "toolbars/SelectionBar.h"
2019-06-09 14:25:01 +00:00
# include "widgets/AudacityMessageBox.h"
# include "widgets/ErrorDialog.h"
# include "widgets/FileHistory.h"
# include "widgets/Warning.h"
# include "xml/XMLFileReader.h"
static const AudacityProject : : AttachedObjects : : RegisteredFactory sFileManagerKey {
[ ] ( AudacityProject & parent ) {
auto result = std : : make_shared < ProjectFileManager > ( parent ) ;
return result ;
}
} ;
ProjectFileManager & ProjectFileManager : : Get ( AudacityProject & project )
{
return project . AttachedObjects : : Get < ProjectFileManager > ( sFileManagerKey ) ;
}
const ProjectFileManager & ProjectFileManager : : Get ( const AudacityProject & project )
{
return Get ( const_cast < AudacityProject & > ( project ) ) ;
}
ProjectFileManager : : ProjectFileManager ( AudacityProject & project )
: mProject { project }
{
}
ProjectFileManager : : ~ ProjectFileManager ( ) = default ;
auto ProjectFileManager : : ReadProjectFile ( const FilePath & fileName )
- > ReadProjectResults
{
auto & project = mProject ;
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 ,
_ ( " Error decoding file " ) ,
wxOK | wxCENTRE , & window ) ;
// Important: Prevent deleting any temporary files!
DirManager : : SetDontDeleteTempFiles ( ) ;
return { true } ;
}
}
///
/// Parse project file
///
XMLFileReader xmlFile ;
2019-07-29 18:04:30 +00:00
# ifdef EXPERIMENTAL_OD_DATA
2019-06-09 14:25:01 +00:00
// 'Lossless copy' projects have dependencies. We need to always copy-in
// these dependencies when converting to a normal project.
2019-04-02 22:40:42 +00:00
auto oldAction = FileFormatsCopyOrEditSetting . Read ( ) ;
2019-06-09 14:25:01 +00:00
bool oldAsk =
gPrefs - > ReadBool ( wxT ( " /Warnings/CopyOrEditUncompressedDataAsk " ) , true ) ;
2019-07-29 18:04:30 +00:00
2019-06-09 14:25:01 +00:00
if ( oldAction ! = wxT ( " copy " ) )
2019-04-02 22:40:42 +00:00
FileFormatsCopyOrEditSetting . Write ( wxT ( " copy " ) ) ;
2019-06-09 14:25:01 +00:00
if ( oldAsk )
gPrefs - > Write ( wxT ( " /Warnings/CopyOrEditUncompressedDataAsk " ) , ( long ) false ) ;
gPrefs - > Flush ( ) ;
auto cleanup = finally ( [ & ] {
// and restore old settings if necessary.
if ( oldAction ! = wxT ( " copy " ) )
2019-04-02 22:40:42 +00:00
FileFormatsCopyOrEditSetting . Write ( oldAction ) ;
2019-06-09 14:25:01 +00:00
if ( oldAsk )
gPrefs - > Write ( wxT ( " /Warnings/CopyOrEditUncompressedDataAsk " ) , ( long ) true ) ;
gPrefs - > Flush ( ) ;
} ) ;
2019-07-29 18:04:30 +00:00
# endif
2019-06-09 14:25:01 +00:00
bool bParseSuccess = xmlFile . Parse ( & projectFileIO , fileName ) ;
bool err = false ;
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 ( ) ;
auto & tracks = TrackList : : Get ( project ) ;
for ( auto t : tracks . Any ( ) ) {
if ( t - > GetErrorOpening ( ) )
{
wxLogWarning (
wxT ( " Track %s had error reading clip values from project file. " ) ,
t - > GetName ( ) ) ;
err = true ;
}
err = ( ! t - > LinkConsistencyCheck ( ) ) | | err ;
mLastSavedTracks - > Add ( t - > Duplicate ( ) ) ;
}
}
return { false , bParseSuccess , err , xmlFile . GetErrorStr ( ) } ;
}
2019-06-05 15:54:13 +00:00
///gets an int with OD flags so that we can determine which ODTasks should be run on this track after save/open, etc.
unsigned int ProjectFileManager : : GetODFlags ( const WaveTrack & track )
{
unsigned int ret = 0 ;
for ( const auto & clip : track . GetClips ( ) )
{
auto sequence = clip - > GetSequence ( ) ;
const auto & blocks = sequence - > GetBlockArray ( ) ;
for ( const auto & block : blocks ) {
const auto & file = block . f ;
if ( ! file - > IsDataAvailable ( ) )
ret | = ( static_cast < ODDecodeBlockFile * > ( & * file ) ) - > GetDecodeType ( ) ;
else if ( ! file - > IsSummaryAvailable ( ) )
ret | = ODTask : : eODPCMSummary ;
}
}
return ret ;
}
2019-06-09 14:25:01 +00:00
void ProjectFileManager : : EnqueueODTasks ( )
{
//check the ODManager to see if we should add the tracks to the ODManager.
//this flag would have been set in the HandleXML calls from above, if there were
//OD***Blocks.
if ( ODManager : : HasLoadedODFlag ( ) )
{
auto & project = mProject ;
auto & tracks = TrackList : : Get ( project ) ;
std : : vector < std : : unique_ptr < ODTask > > newTasks ;
//std::vector<ODDecodeTask*> decodeTasks;
unsigned int createdODTasks = 0 ;
for ( auto wt : tracks . Any < WaveTrack > ( ) ) {
//check the track for blocks that need decoding.
//There may be more than one type e.g. FLAC/FFMPEG/lame
2019-06-05 15:54:13 +00:00
unsigned int odFlags = GetODFlags ( * wt ) ;
2019-06-09 14:25:01 +00:00
//add the track to the already created tasks that correspond to the od flags in the wavetrack.
for ( unsigned int i = 0 ; i < newTasks . size ( ) ; i + + ) {
if ( newTasks [ i ] - > GetODType ( ) & odFlags )
2019-06-05 15:46:07 +00:00
newTasks [ i ] - > AddWaveTrack ( wt - > SharedPointer < WaveTrack > ( ) ) ;
2019-06-09 14:25:01 +00:00
}
//create whatever NEW tasks we need to.
//we want at most one instance of each class for the project
while ( ( odFlags | createdODTasks ) ! = createdODTasks )
{
std : : unique_ptr < ODTask > newTask ;
# ifdef EXPERIMENTAL_OD_FLAC
if ( ! ( createdODTasks & ODTask : : eODFLAC ) & & ( odFlags & ODTask : : eODFLAC ) ) {
newTask = std : : make_unique < ODDecodeFlacTask > ( ) ;
createdODTasks = createdODTasks | ODTask : : eODFLAC ;
}
else
# endif
if ( ! ( createdODTasks & ODTask : : eODPCMSummary ) & & ( odFlags & ODTask : : eODPCMSummary ) ) {
newTask = std : : make_unique < ODComputeSummaryTask > ( ) ;
createdODTasks = createdODTasks | ODTask : : eODPCMSummary ;
}
else {
wxPrintf ( " unrecognized OD Flag in block file. \n " ) ;
//TODO:ODTODO: display to user. This can happen when we build audacity on a system that doesnt have libFLAC
break ;
}
if ( newTask )
{
2019-06-05 15:46:07 +00:00
newTask - > AddWaveTrack ( wt - > SharedPointer < WaveTrack > ( ) ) ;
2019-06-09 14:25:01 +00:00
newTasks . push_back ( std : : move ( newTask ) ) ;
}
}
}
for ( unsigned int i = 0 ; i < newTasks . size ( ) ; i + + )
ODManager : : Instance ( ) - > AddNewTask ( std : : move ( newTasks [ i ] ) ) ;
}
}
bool ProjectFileManager : : Save ( )
{
// Prompt for file name?
bool bPromptingRequired = ! ProjectFileIO : : Get ( mProject ) . IsProjectSaved ( ) ;
if ( bPromptingRequired )
return SaveAs ( ) ;
return DoSave ( false , false ) ;
}
#if 0
// I added this to "fix" bug #334. At that time, we were on wxWidgets 2.8.12 and
// there was a window between the closing of the "Save" progress dialog and the
// end of the actual save where the user was able to close the project window and
// recursively enter the Save code (where they could inadvertently cause the issue
// described in #334).
//
// When we converted to wx3, this "disabler" caused focus problems when returning
// to the project after the save (bug #1172) because the focus and activate events
// weren't being dispatched and the focus would get lost.
//
// After some testing, it looks like the window described above no longer exists,
// so I've disabled the disabler. However, I'm leaving it here in case we run
// into the problem in the future. (even though it can't be used as-is)
class ProjectDisabler
{
public :
ProjectDisabler ( wxWindow * w )
: mWindow ( w )
{
mWindow - > GetEventHandler ( ) - > SetEvtHandlerEnabled ( false ) ;
}
~ ProjectDisabler ( )
{
mWindow - > GetEventHandler ( ) - > SetEvtHandlerEnabled ( true ) ;
}
private :
wxWindow * mWindow ;
} ;
# endif
// Assumes AudacityProject::mFileName has been set to the desired path.
bool ProjectFileManager : : DoSave ( const bool fromSaveAs ,
const bool bWantSaveCopy ,
const bool bLossless /*= false*/ )
{
// 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 availabele from SaveAs " ) ;
// Some confirmation dialogs
if ( ! bWantSaveCopy )
{
auto & tracks = TrackList : : Get ( proj ) ;
if ( ! tracks . Any ( ) )
{
if ( UndoManager : : Get ( proj ) . UnsavedChanges ( )
& & settings . EmptyCanBeDirty ( ) ) {
int result = AudacityMessageBox ( _ ( " Your project is now empty. \n If saved, the project will have no tracks. \n \n To save any previously open tracks: \n Click 'No', Edit > Undo until all tracks \n are open, then File > Save Project. \n \n Save anyway? " ) ,
_ ( " 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 ) ) {
# ifdef __WXGTK__
safetyFileName = fileName + wxT ( " ~ " ) ;
# else
safetyFileName = fileName + wxT ( " .bak " ) ;
# endif
2019-09-01 14:28:00 +00:00
bool bOK = true ;
2019-06-09 14:25:01 +00:00
if ( wxFileExists ( safetyFileName ) )
2019-09-01 14:28:00 +00:00
bOK = wxRemoveFile ( safetyFileName ) ;
2019-06-09 14:25:01 +00:00
if ( ! wxRenameFile ( fileName , safetyFileName ) ) {
AudacityMessageBox (
wxString : : Format (
2019-09-01 14:28:00 +00:00
_ ( " Audacity failed to write file %s. \n Perhaps disk is full or not writable. " ) , safetyFileName ) ,
_ ( " Error Writing to File " ) , wxICON_STOP , & window ) ;
2019-06-09 14:25:01 +00:00
return false ;
}
}
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 ( wxString : : Format ( _ ( " Could not save project. Perhaps %s \n is not writable or the disk is full. " ) ,
project ) ,
_ ( " 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 ( wxString : : Format (
_ ( " Could not save project. Path not found. Try creating \n directory \" %s \" before saving project with this name. " ) ,
projPath ) ,
_ ( " Error Saving Project " ) ,
wxICON_ERROR , & window ) ;
return ( success = false ) ;
}
if ( bWantSaveCopy )
{
// 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 ) ;
}
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 , _ ( " 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 < std : : unique_ptr < WaveTrack : : Locker > > lockers ;
Maybe < DirManager : : ProjectSetter > 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 < WaveTrack > ( ) )
lockers . push_back (
std : : make_unique < WaveTrack : : Locker > ( wt ) ) ;
}
// This renames the project directory, and moves or copies
// all of our block files over.
pSetter . create ( 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 ;
} ) ;
if ( ! success )
return false ;
// SAVE HAS SUCCEEDED -- following are further no-fail commit operations.
if ( pSetter )
pSetter - > Commit ( ) ;
}
if ( ! bWantSaveCopy )
{
// Now that we have saved the file, we can DELETE the auto-saved version
projectFileIO . DeleteCurrentAutoSaveFile ( ) ;
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 ( ) ;
auto & tracks = TrackList : : Get ( proj ) ;
for ( auto t : tracks . Any ( ) ) {
mLastSavedTracks - > Add ( t - > Duplicate ( ) ) ;
//only after the xml has been saved we can mark it saved.
//thus is because the OD blockfiles change on background thread while this is going on.
// if(const auto wt = track_cast<WaveTrack*>(dupT))
// wt->MarkSaved();
}
UndoManager : : Get ( proj ) . StateSaved ( ) ;
}
// 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 ( " " ) ;
2019-07-02 16:44:55 +00:00
ProjectStatus : : Get ( proj ) . Set (
wxString : : Format ( _ ( " Saved %s " ) , fileName ) ) ;
2019-06-09 14:25:01 +00:00
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 ;
# ifdef USE_LIBVORBIS
if ( bLossless ) {
extension = wxT ( " wav " ) ;
fileFormat = wxT ( " WAVFLT " ) ;
} else {
extension = wxT ( " ogg " ) ;
fileFormat = wxT ( " OGG " ) ;
}
# else
extension = wxT ( " wav " ) ;
fileFormat = wxT ( " WAVFLT " ) ;
# endif
// Some of this is similar to code in ExportMultiple::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 ( ) ;
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 < const WaveTrack > ( ) ;
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 ;
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 ( & project , 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*/ )
{
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 ( ) ;
bool bOwnsNewAupName = bLoadedFromAup & & ( 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 ) ) {
AudacityMessageDialog m (
NULL ,
_ ( " The project was not saved because the file name provided would overwrite another project. \n Please try again and select an original name. " ) ,
_ ( " Error Saving Project " ) ,
wxOK | wxICON_ERROR ) ;
m . ShowModal ( ) ;
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 ) ;
if ( success & & addToHistory ) {
FileHistory : : Global ( ) . AddFileToHistory ( project . GetFileName ( ) ) ;
}
if ( ! success | | bWantSaveCopy ) // bWantSaveCopy doesn't actually change current project.
{
}
else {
projectFileIO . SetLoadedFromAup ( true ) ;
projectFileIO . SetProjectTitle ( ) ;
}
return ( success ) ;
}
bool ProjectFileManager : : SaveAs ( bool bWantSaveCopy /*= false*/ , bool bLossless /*= false*/ )
{
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 ;
bool bLoadedFromAup = projectFileIO . IsLoadedFromAup ( ) ;
// 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 " ) ) ;
}
wxString title ;
wxString message ;
if ( bWantSaveCopy )
{
if ( bLossless )
{
title = wxString : : Format ( _ ( " %sSave Lossless Copy of Project \" %s \" As... " ) ,
Restorer . sProjNumber , Restorer . sProjName ) ;
message = _ ( " \
' 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 = wxString : : Format ( _ ( " %sSave Compressed Copy of Project \" %s \" As... " ) ,
Restorer . sProjNumber , Restorer . sProjName ) ;
message = _ ( " \
' 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 = wxString : : Format ( _ ( " %sSave Project \" %s \" As... " ) ,
Restorer . sProjNumber , Restorer . sProjName ) ;
message = _ ( " \
' 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 ( ) ) ;
wxString fName ;
if ( bPrompt ) {
// JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
// for overwrite ourselves later, and we disallow it.
// We disallow overwrite because we would have to DELETE the many
// smaller files too, or prompt to move them.
fName = FileNames : : SelectFile ( FileNames : : Operation : : Export ,
title ,
filename . GetPath ( ) ,
filename . GetFullName ( ) ,
wxT ( " aup " ) ,
_ ( " Audacity projects " ) + wxT ( " (*.aup)|*.aup " ) ,
wxFD_SAVE | wxRESIZE_BORDER ,
& window ) ;
if ( fName . empty ( ) )
return false ;
filename = fName ;
} ;
filename . SetExt ( wxT ( " aup " ) ) ;
fName = filename . GetFullPath ( ) ;
if ( ( bWantSaveCopy | | ! bPrompt ) & & filename . FileExists ( ) ) {
// Saving a copy of the project should never overwrite an existing project.
AudacityMessageDialog m (
NULL ,
_ ( " Saving a copy must not overwrite an existing saved project. \n Please try again and select an original name. " ) ,
_ ( " Error Saving Copy of Project " ) ,
wxOK | wxICON_ERROR ) ;
m . ShowModal ( ) ;
return false ;
}
bool bOwnsNewAupName = bLoadedFromAup & & ( project . 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 ( ) ) {
// 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 ;
for ( auto p : AllProjects { } ) {
const wxFileName openProjectName { p - > GetFileName ( ) } ;
if ( openProjectName . SameAs ( fName ) ) {
mayOverwrite - = 1 ;
if ( mayOverwrite = = 0 )
break ;
}
}
if ( mayOverwrite > 0 ) {
/* i18n-hint: In each case, %s is the name
of the file being overwritten . */
wxString Message = wxString : : Format ( _ ( " \
Do you want to overwrite the project : \ n \ " %s \" ? \n \n \
If you select \ " Yes \" the project \n \" %s \" \n \
will be irreversibly overwritten . " ), fName, fName);
// For safety, there should NOT be an option to hide this warning.
int result = AudacityMessageBox ( Message ,
/* i18n-hint: Heading: A warning that a project is about to be overwritten.*/
_ ( " Overwrite Project Warning " ) ,
wxYES_NO | wxNO_DEFAULT | wxICON_WARNING ,
& window ) ;
if ( result ! = wxYES ) {
return false ;
}
}
else
{
// Overwrite disalowed. The destination project is open in another window.
AudacityMessageDialog m (
NULL ,
_ ( " The project will not saved because the selected project is open in another window. \n Please try again and select an original name. " ) ,
_ ( " Error Saving Project " ) ,
wxOK | wxICON_ERROR ) ;
m . ShowModal ( ) ;
return false ;
}
}
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 ) ;
if ( success ) {
FileHistory : : Global ( ) . AddFileToHistory ( project . 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 ) ;
}
void ProjectFileManager : : Reset ( )
{
// mLastSavedTrack code copied from OnCloseWindow.
// Lock all blocks in all tracks of the last saved version, so that
// 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.
CloseLock ( ) ;
ProjectFileIO : : Get ( mProject ) . Reset ( ) ;
}
bool ProjectFileManager : : SaveFromTimerRecording ( wxFileName fnFile )
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
// MY: Will save the project to a NEW location a-la Save As
// and then tidy up after itself.
wxString sNewFileName = fnFile . GetFullPath ( ) ;
// 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 ( ) ;
}
// MY: If the project file already exists then bail out
// and send populate the message string (pointer) so
// we can tell the user what went wrong.
if ( wxFileExists ( sNewFileName ) ) {
return false ;
}
project . SetFileName ( sNewFileName ) ;
bool bSuccess = false ;
auto cleanup = finally ( [ & ] {
if ( ! bSuccess )
// Restore file name on error
project . SetFileName ( sOldFilename ) ;
} ) ;
bSuccess = DoSave ( true , false ) ;
if ( bSuccess ) {
FileHistory : : Global ( ) . AddFileToHistory ( project . GetFileName ( ) ) ;
projectFileIO . SetLoadedFromAup ( true ) ;
projectFileIO . SetProjectTitle ( ) ;
}
return bSuccess ;
}
void ProjectFileManager : : CloseLock ( )
{
// Lock all blocks in all tracks of the last saved version, so that
// 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 ) {
for ( auto wt : mLastSavedTracks - > Any < WaveTrack > ( ) )
wt - > CloseLock ( ) ;
mLastSavedTracks - > Clear ( ) ;
mLastSavedTracks . reset ( ) ;
}
}
2019-06-08 20:05:22 +00:00
// static method, can be called outside of a project
wxArrayString ProjectFileManager : : ShowOpenDialog ( const wxString & extraformat , const wxString & extrafilter )
{
FormatList l ;
wxString filter ; ///< List of file format names and extensions, separated
/// by | characters between _formats_ and extensions for each _format_, i.e.
/// format1name | *.ext | format2name | *.ex1;*.ex2
wxString all ; ///< One long list of all supported file extensions,
/// semicolon separated
if ( ! extraformat . empty ( ) )
{ // additional format specified
all = extrafilter + wxT ( ' ; ' ) ;
// add it to the "all supported files" filter string
}
// Construct the filter
Importer : : Get ( ) . GetSupportedImportFormats ( & l ) ;
for ( const auto & format : l ) {
/* this loop runs once per supported _format_ */
const Format * f = & format ;
wxString newfilter = f - > formatName + wxT ( " | " ) ;
// bung format name into string plus | separator
for ( size_t i = 0 ; i < f - > formatExtensions . size ( ) ; i + + ) {
/* this loop runs once per valid _file extension_ for file containing
* the current _format_ */
if ( ! newfilter . Contains ( wxT ( " *. " ) + f - > formatExtensions [ i ] + wxT ( " ; " ) ) )
newfilter + = wxT ( " *. " ) + f - > formatExtensions [ i ] + wxT ( " ; " ) ;
if ( ! all . Contains ( wxT ( " *. " ) + f - > formatExtensions [ i ] + wxT ( " ; " ) ) )
all + = wxT ( " *. " ) + f - > formatExtensions [ i ] + wxT ( " ; " ) ;
}
newfilter . RemoveLast ( 1 ) ;
filter + = newfilter ;
filter + = wxT ( " | " ) ;
}
all . RemoveLast ( 1 ) ;
filter . RemoveLast ( 1 ) ;
// For testing long filters
#if 0
wxString test = wxT ( " *.aaa;*.bbb;*.ccc;*.ddd;*.eee " ) ;
all = test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) +
test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) +
test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) + test + wxT ( ' ; ' ) +
all ;
# endif
/* i18n-hint: The vertical bars and * are essential here.*/
wxString mask = _ ( " All files|*|All supported files| " ) +
all + wxT ( " | " ) ; // "all" and "all supported" entries
if ( ! extraformat . empty ( ) )
{ // append caller-defined format if supplied
mask + = extraformat + wxT ( " | " ) + extrafilter + wxT ( " | " ) ;
}
mask + = filter ; // put the names and extensions of all the importer formats
// we built up earlier into the mask
// Retrieve saved path and type
auto path = FileNames : : FindDefaultPath ( FileNames : : Operation : : Open ) ;
wxString type = gPrefs - > Read ( wxT ( " /DefaultOpenType " ) , mask . BeforeFirst ( wxT ( ' | ' ) ) ) ;
// Convert the type to the filter index
int index = mask . First ( type + wxT ( " | " ) ) ;
if ( index = = wxNOT_FOUND ) {
index = 0 ;
}
else {
index = mask . Left ( index ) . Freq ( wxT ( ' | ' ) ) / 2 ;
if ( index < 0 ) {
index = 0 ;
}
}
// Construct and display the file dialog
wxArrayString selected ;
FileDialogWrapper dlog ( NULL ,
_ ( " Select one or more files " ) ,
path ,
wxT ( " " ) ,
mask ,
wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER ) ;
dlog . SetFilterIndex ( index ) ;
int dialogResult = dlog . ShowModal ( ) ;
// Convert the filter index to type and save
index = dlog . GetFilterIndex ( ) ;
for ( int i = 0 ; i < index ; i + + ) {
mask = mask . AfterFirst ( wxT ( ' | ' ) ) . AfterFirst ( wxT ( ' | ' ) ) ;
}
gPrefs - > Write ( wxT ( " /DefaultOpenType " ) , mask . BeforeFirst ( wxT ( ' | ' ) ) ) ;
gPrefs - > Write ( wxT ( " /LastOpenType " ) , mask . BeforeFirst ( wxT ( ' | ' ) ) ) ;
gPrefs - > Flush ( ) ;
if ( dialogResult = = wxID_OK ) {
// Return the selected files
dlog . GetPaths ( selected ) ;
}
return selected ;
}
// static method, can be called outside of a project
bool ProjectFileManager : : IsAlreadyOpen ( const FilePath & projPathName )
{
const wxFileName newProjPathName ( 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 ( ) } ) ;
} ) ;
if ( iter ! = finish ) {
wxString errMsg =
wxString : : Format ( _ ( " %s is already open in another window. " ) ,
newProjPathName . GetName ( ) ) ;
wxLogError ( errMsg ) ;
AudacityMessageBox ( errMsg , _ ( " Error Opening Project " ) , wxOK | wxCENTRE ) ;
return true ;
}
return false ;
}
XMLTagHandler *
ProjectFileManager : : RecordingRecoveryFactory ( AudacityProject & project ) {
auto & ProjectFileManager = Get ( project ) ;
auto & ptr = ProjectFileManager . mRecordingRecoveryHandler ;
if ( ! ptr )
ptr =
std : : make_unique < RecordingRecoveryHandler > ( & project ) ;
return ptr . get ( ) ;
}
ProjectFileIORegistry : : Entry
ProjectFileManager : : sRecoveryFactory {
wxT ( " recordingrecovery " ) , RecordingRecoveryFactory
} ;
// XML handler for <import> tag
class ImportXMLTagHandler final : public XMLTagHandler
{
public :
ImportXMLTagHandler ( AudacityProject * pProject ) { mProject = pProject ; }
bool HandleXMLTag ( const wxChar * tag , const wxChar * * attrs ) override ;
XMLTagHandler * HandleXMLChild ( const wxChar * WXUNUSED ( tag ) ) override
{ return NULL ; }
// Don't want a WriteXML method because ImportXMLTagHandler is not a WaveTrack.
// <import> tags are instead written by AudacityProject::WriteXML.
// void WriteXML(XMLWriter &xmlFile) /* not override */ { wxASSERT(false); }
private :
AudacityProject * mProject ;
} ;
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 ;
// Guard this call so that C++ exceptions don't propagate through
// the expat library
GuardedCall (
[ & ] {
ProjectFileManager : : Get ( * mProject ) . Import ( strAttr , & trackArray ) ; } ,
[ & ] ( AudacityException * ) { trackArray . clear ( ) ; }
) ;
if ( trackArray . empty ( ) )
return false ;
// Handle other attributes, now that we have the tracks.
attrs + + ;
const wxChar * * pAttr ;
bool bSuccess = true ;
for ( size_t i = 0 ; i < trackArray . size ( ) ; i + + )
{
// Most of the "import" tag attributes are the same as for "wavetrack" tags,
// so apply them via WaveTrack::HandleXMLTag().
bSuccess = trackArray [ i ] - > HandleXMLTag ( wxT ( " wavetrack " ) , attrs ) ;
// "offset" tag is ignored in WaveTrack::HandleXMLTag except for legacy projects,
// so handle it here.
double dblValue ;
pAttr = attrs ;
while ( * pAttr )
{
const wxChar * attr = * pAttr + + ;
const wxChar * value = * pAttr + + ;
const wxString strValue = value ;
if ( ! wxStrcmp ( attr , wxT ( " offset " ) ) & &
XMLValueChecker : : IsGoodString ( strValue ) & &
Internat : : CompatibleToDouble ( strValue , & dblValue ) )
trackArray [ i ] - > SetOffset ( dblValue ) ;
}
}
return bSuccess ;
} ;
XMLTagHandler *
ProjectFileManager : : ImportHandlerFactory ( AudacityProject & project ) {
auto & ProjectFileManager = Get ( project ) ;
auto & ptr = ProjectFileManager . mImportXMLTagHandler ;
if ( ! ptr )
ptr =
std : : make_unique < ImportXMLTagHandler > ( & 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 )
{
auto & project = mProject ;
auto & history = ProjectHistory : : Get ( project ) ;
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
// occassions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
// convert these to long file name first.
auto fileName = PlatformCompatibility : : ConvertSlashInFileName (
PlatformCompatibility : : GetLongFileName ( fileNameArg ) ) ;
// Make sure it isn't already open.
// Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
// and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
// from, e.g., Win Explorer, it would bypass those, get to here with no check,
// then open a NEW project from the same data with no warning.
// This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
// but is not really part of that bug. Anyway, prevent it!
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.
if ( fileName . Lower ( ) . EndsWith ( wxT ( " .aup.bak " ) ) )
{
AudacityMessageBox (
_ ( " You are trying to open an automatically created backup file. \n Doing this may result in severe data loss. \n \n Please open the actual Audacity project file instead. " ) ,
_ ( " Warning - Backup File Detected " ) ,
wxOK | wxCENTRE , & window ) ;
return ;
}
if ( ! : : wxFileExists ( fileName ) ) {
AudacityMessageBox (
wxString : : Format ( _ ( " Could not open file: %s " ) , fileName ) ,
( " Error Opening File " ) ,
wxOK | wxCENTRE , & window ) ;
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 " ) ) ;
if ( ! ff . IsOpened ( ) ) {
AudacityMessageBox (
wxString : : Format ( _ ( " Could not open file: %s " ) , fileName ) ,
_ ( " Error opening file " ) ,
wxOK | wxCENTRE , & window ) ;
return ;
}
int numRead = ff . Read ( buf , 15 ) ;
if ( numRead ! = 15 ) {
AudacityMessageBox ( wxString : : Format ( _ ( " File may be invalid or corrupted: \n %s " ) ,
fileName ) , _ ( " 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 ( _ ( " Audacity was unable to convert an Audacity 1.0 project to the new project format. " ) ,
_ ( " Error Opening Project " ) ,
wxOK | wxCENTRE , & window ) ;
return ;
}
else {
temp = wxT ( " <?xml " ) ;
}
}
// FIXME: //v Surely we could be smarter about this, like checking much earlier that this is a .aup file.
if ( temp . Mid ( 0 , 6 ) ! = wxT ( " <?xml " ) ) {
// If it's not XML, try opening it as any other form of audio
# ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
// Is it a plug-in?
if ( PluginManager : : Get ( ) . DropFile ( fileName ) ) {
MenuCreator : : RebuildAllMenuBars ( ) ;
}
else
// No, so import.
# endif
{
# ifdef USE_MIDI
if ( FileNames : : IsMidi ( fileName ) )
2019-06-25 02:31:11 +00:00
DoImportMIDI ( project , fileName ) ;
2019-06-08 20:05:22 +00:00
else
# endif
Import ( fileName ) ;
window . ZoomAfterImport ( nullptr ) ;
}
return ;
}
// The handlers may be created during ReadProjectFile and are not needed
// after this function exits.
auto cleanupHandlers = finally ( [ this ] {
mImportXMLTagHandler . reset ( ) ;
mRecordingRecoveryHandler . reset ( ) ;
} ) ;
auto results = ReadProjectFile ( fileName ) ;
if ( results . decodeError )
return ;
const bool bParseSuccess = results . parseSuccess ;
const wxString & errorStr = results . errorString ;
const bool err = results . trackError ;
if ( bParseSuccess ) {
auto & settings = ProjectSettings : : Get ( project ) ;
window . mbInitializingScrollbar = true ; // this must precede AS_SetSnapTo
// to make persistence of the vertical scrollbar position work
auto & selectionManager = ProjectSelectionManager : : Get ( project ) ;
selectionManager . AS_SetSnapTo ( settings . GetSnapTo ( ) ) ;
selectionManager . AS_SetSelectionFormat ( settings . GetSelectionFormat ( ) ) ;
selectionManager . SSBL_SetFrequencySelectionFormatName (
settings . GetFrequencySelectionFormatName ( ) ) ;
selectionManager . SSBL_SetBandwidthSelectionFormatName (
settings . GetBandwidthSelectionFormatName ( ) ) ;
SelectionBar : : Get ( project ) . SetRate ( settings . GetRate ( ) ) ;
ProjectHistory : : Get ( project ) . InitialState ( ) ;
2019-07-01 22:32:18 +00:00
TrackFocus : : Get ( project ) . Set ( * tracks . Any ( ) . begin ( ) ) ;
2019-06-08 20:05:22 +00:00
window . HandleResize ( ) ;
trackPanel . Refresh ( false ) ;
trackPanel . Update ( ) ; // force any repaint to happen now,
// else any asynch calls into the blockfile code will not have
// finished logging errors (if any) before the call to ProjectFSCK()
if ( addtohistory )
FileHistory : : Global ( ) . AddFileToHistory ( fileName ) ;
}
// Use a finally block here, because there are calls to Save() below which
// might throw.
bool closed = false ;
auto cleanup = finally ( [ & ] {
//release the flag.
ODManager : : UnmarkLoadedODFlag ( ) ;
if ( ! closed ) {
if ( bParseSuccess ) {
// This is a no-fail:
dirManager . FillBlockfilesCache ( ) ;
EnqueueODTasks ( ) ;
}
// For an unknown reason, OSX requires that the project window be
// raised if a recovery took place.
window . CallAfter ( [ & ] { window . Raise ( ) ; } ) ;
}
} ) ;
if ( bParseSuccess ) {
bool saved = false ;
if ( projectFileIO . IsRecovered ( ) )
{
// This project has been recovered, so write a NEW auto-save file
// now and then DELETE the old one in the auto-save folder. Note that
// at this point mFileName != fileName, because when opening a
// recovered file mFileName is faked to point to the original file
// which has been recovered, not the one in the auto-save folder.
: : ProjectFSCK ( dirManager , err , true ) ; // Correct problems in auto-recover mode.
// PushState calls AutoSave(), so no longer need to do so here.
history . PushState ( _ ( " Project was recovered " ) , _ ( " Recover " ) ) ;
if ( ! wxRemoveFile ( fileName ) )
AudacityMessageBox ( _ ( " Could not remove old auto save file " ) ,
_ ( " Error " ) , wxICON_STOP , & window ) ;
}
else
{
// This is a regular project, check it and ask user
int status = : : ProjectFSCK ( dirManager , err , false ) ;
if ( status & FSCKstatus_CLOSE_REQ )
{
// Vaughan, 2010-08-23: Note this did not do a real close.
// It could cause problems if you get this, say on missing alias files,
// then try to open a project with, e.g., missing blockfiles.
// It then failed in SetProject, saying it cannot find the files,
// then never go through ProjectFSCK to give more info.
// Going through OnClose() may be overkill, but it's safe.
/*
// There was an error in the load/check and the user
// explictly opted to close the project.
mTracks - > Clear ( true ) ;
mFileName = wxT ( " " ) ;
SetProjectTitle ( ) ;
mTrackPanel - > Refresh ( true ) ;
*/
closed = true ;
SetMenuClose ( true ) ;
window . Close ( ) ;
return ;
}
else if ( status & FSCKstatus_CHANGED )
{
// Mark the wave tracks as changed and redraw.
for ( auto wt : tracks . Any < WaveTrack > ( ) )
// Only wave tracks have a notion of "changed".
for ( const auto & clip : wt - > GetClips ( ) )
clip - > MarkChanged ( ) ;
trackPanel . Refresh ( true ) ;
// Vaughan, 2010-08-20: This was bogus, as all the actions in ProjectFSCK
// that return FSCKstatus_CHANGED cannot be undone.
// this->PushState(_("Project checker repaired file"), _("Project Repair"));
if ( status & FSCKstatus_SAVE_AUP )
Save ( ) , saved = true ;
}
}
if ( mImportXMLTagHandler ) {
if ( ! saved )
// We processed an <import> tag, so save it as a normal project,
// with no <import> tags.
Save ( ) ;
}
}
else {
// Vaughan, 2011-10-30:
// See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
// Calling mTracks->Clear() with deleteTracks true results in data loss.
// PRL 2014-12-19:
// I made many changes for wave track memory management, but only now
// read the above comment. I may have invalidated the fix above (which
// may have spared the files at the expense of leaked memory). But
// here is a better way to accomplish the intent, doing like what happens
// when the project closes:
for ( auto pTrack : tracks . Any < WaveTrack > ( ) )
pTrack - > CloseLock ( ) ;
tracks . Clear ( ) ; //tracks.Clear(true);
project . SetFileName ( wxT ( " " ) ) ;
projectFileIO . SetProjectTitle ( ) ;
wxLogError ( wxT ( " Could not parse file \" %s \" . \n Error: %s " ) , fileName , errorStr ) ;
wxString url = wxT ( " FAQ:Errors_on_opening_or_recovering_an_Audacity_project " ) ;
// Certain errors have dedicated help.
// On April-4th-2018, we did not request translation of the XML errors.
// If/when we do, we will need _() around the comparison strings.
if ( errorStr . Contains ( ( " not well-formed (invalid token) " ) ) )
url = " Error:_not_well-formed_(invalid_token)_at_line_x " ;
else if ( errorStr . Contains ( ( " reference to invalid character number " ) ) )
url = " Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x " ;
else if ( errorStr . Contains ( ( " mismatched tag " ) ) )
url + = " #mismatched " ;
// These two errors with FAQ entries are reported elsewhere, not here....
//#[[#import-error|Error Importing: Aup is an Audacity Project file. Use the File > Open command]]
//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
// If we did want to handle every single parse error, these are they....
/*
XML_L ( " out of memory " ) ,
XML_L ( " syntax error " ) ,
XML_L ( " no element found " ) ,
XML_L ( " not well-formed (invalid token) " ) ,
XML_L ( " unclosed token " ) ,
XML_L ( " partial character " ) ,
XML_L ( " mismatched tag " ) ,
XML_L ( " duplicate attribute " ) ,
XML_L ( " junk after document element " ) ,
XML_L ( " illegal parameter entity reference " ) ,
XML_L ( " undefined entity " ) ,
XML_L ( " recursive entity reference " ) ,
XML_L ( " asynchronous entity " ) ,
XML_L ( " reference to invalid character number " ) ,
XML_L ( " reference to binary entity " ) ,
XML_L ( " reference to external entity in attribute " ) ,
XML_L ( " XML or text declaration not at start of entity " ) ,
XML_L ( " unknown encoding " ) ,
XML_L ( " encoding specified in XML declaration is incorrect " ) ,
XML_L ( " unclosed CDATA section " ) ,
XML_L ( " error in processing external entity reference " ) ,
XML_L ( " document is not standalone " ) ,
XML_L ( " unexpected parser state - please send a bug report " ) ,
XML_L ( " entity declared in parameter entity " ) ,
XML_L ( " requested feature requires XML_DTD support in Expat " ) ,
XML_L ( " cannot change setting once parsing has begun " ) ,
XML_L ( " unbound prefix " ) ,
XML_L ( " must not undeclare prefix " ) ,
XML_L ( " incomplete markup in parameter entity " ) ,
XML_L ( " XML declaration not well-formed " ) ,
XML_L ( " text declaration not well-formed " ) ,
XML_L ( " illegal character(s) in public id " ) ,
XML_L ( " parser suspended " ) ,
XML_L ( " parser not suspended " ) ,
XML_L ( " parsing aborted " ) ,
XML_L ( " parsing finished " ) ,
XML_L ( " cannot suspend in external parameter entity " ) ,
XML_L ( " reserved prefix (xml) must not be undeclared or bound to another namespace name " ) ,
XML_L ( " reserved prefix (xmlns) must not be declared or undeclared " ) ,
XML_L ( " prefix must not be bound to one of the reserved namespace names " )
*/
ShowErrorDialog (
& window ,
_ ( " Error Opening Project " ) ,
errorStr ,
url ) ;
}
}
std : : vector < std : : shared_ptr < Track > >
ProjectFileManager : : AddImportedTracks ( const FilePath & fileName ,
TrackHolders & & newTracks )
{
auto & project = mProject ;
auto & history = ProjectHistory : : Get ( project ) ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
auto & tracks = TrackList : : Get ( project ) ;
std : : vector < std : : shared_ptr < Track > > results ;
2019-06-21 23:38:38 +00:00
SelectUtilities : : SelectNone ( project ) ;
2019-06-08 20:05:22 +00:00
bool initiallyEmpty = tracks . empty ( ) ;
double newRate = 0 ;
wxString trackNameBase = fileName . AfterLast ( wxFILE_SEP_PATH ) . BeforeLast ( ' . ' ) ;
int i = - 1 ;
// Must add all tracks first (before using Track::IsLeader)
for ( auto & group : newTracks ) {
if ( group . empty ( ) ) {
wxASSERT ( false ) ;
continue ;
}
auto first = group . begin ( ) - > get ( ) ;
auto nChannels = group . size ( ) ;
for ( auto & uNewTrack : group ) {
auto newTrack = tracks . Add ( uNewTrack ) ;
results . push_back ( newTrack - > SharedPointer ( ) ) ;
}
tracks . GroupChannels ( * first , nChannels ) ;
}
newTracks . clear ( ) ;
// Now name them
// Add numbers to track names only if there is more than one (mono or stereo)
// track (not necessarily, more than one channel)
const bool useSuffix =
make_iterator_range ( results . begin ( ) + 1 , results . end ( ) )
. any_of ( [ ] ( decltype ( * results . begin ( ) ) & pTrack )
{ return pTrack - > IsLeader ( ) ; } ) ;
for ( const auto & newTrack : results ) {
if ( newTrack - > IsLeader ( ) )
// Count groups only
+ + i ;
newTrack - > SetSelected ( true ) ;
if ( useSuffix )
newTrack - > SetName ( trackNameBase + wxString : : Format ( wxT ( " %d " ) , i + 1 ) ) ;
else
newTrack - > SetName ( trackNameBase ) ;
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 ) ;
}
}
} ) ;
}
// Automatically assign rate of imported file to whole project,
// if this is the first file that is imported
if ( initiallyEmpty & & newRate > 0 ) {
auto & settings = ProjectSettings : : Get ( project ) ;
settings . SetRate ( newRate ) ;
SelectionBar : : Get ( project ) . SetRate ( newRate ) ;
}
history . PushState ( wxString : : Format ( _ ( " Imported '%s' " ) , fileName ) ,
_ ( " Import " ) ) ;
# if defined(__WXGTK__)
// See bug #1224
// The track panel hasn't we been fully created, so the DoZoomFit() will not give
// expected results due to a window width of zero. Should be safe to yield here to
// allow the creattion to complete. If this becomes a problem, it "might" be possible
// to queue a dummy event to trigger the DoZoomFit().
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 ) ;
projectFileIO . SetProjectTitle ( ) ;
}
// Moved this call to higher levels to prevent flicker redrawing everything on each file.
// HandleResize();
return results ;
}
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
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 ;
wxString errorMessage ;
{
// Backup Tags, before the import. Be prepared to roll back changes.
bool committed = false ;
auto cleanup = finally ( [ & ] {
if ( ! committed )
Tags : : Set ( project , oldTags ) ;
} ) ;
auto newTags = oldTags - > Duplicate ( ) ;
Tags : : Set ( project , newTags ) ;
bool success = Importer : : Get ( ) . Import ( fileName ,
& TrackFactory : : Get ( project ) ,
newTracks ,
newTags . get ( ) ,
errorMessage ) ;
if ( ! errorMessage . empty ( ) ) {
// Error message derived from Importer::Import
// Additional help via a Help button links to the manual.
ShowErrorDialog ( & GetProjectFrame ( project ) , _ ( " Error Importing " ) ,
errorMessage , wxT ( " Importing_Audio " ) ) ;
}
if ( ! success )
return false ;
FileHistory : : Global ( ) . AddFileToHistory ( fileName ) ;
// no more errors, commit
committed = true ;
}
// for LOF ("list of files") files, do not import the file as if it
// were an audio file itself
if ( fileName . AfterLast ( ' . ' ) . IsSameAs ( wxT ( " lof " ) , false ) ) {
// PRL: don't redundantly do the steps below, because we already
// did it in case of LOF, because of some weird recursion back to this
// same function. I think this should be untangled.
// So Undo history push is not bypassed, despite appearances.
return false ;
}
// PRL: Undo history is incremented inside this:
auto newSharedTracks = AddImportedTracks ( fileName , std : : move ( newTracks ) ) ;
if ( pTrackArray ) {
for ( const auto & newTrack : newSharedTracks ) {
newTrack - > TypeSwitch ( [ & ] ( WaveTrack * wt ) {
pTrackArray - > push_back ( wt - > SharedPointer < WaveTrack > ( ) ) ;
} ) ;
}
}
// This is a no-fail:
dirManager . FillBlockfilesCache ( ) ;
return true ;
}