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 <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>
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 "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 "Tags.h"
2021-01-14 15:50:06 +00:00
# include "TempDirectory.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"
# include "WaveTrack.h"
# include "wxFileNameWrapper.h"
# 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 "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 ) ) ;
}
2020-11-19 18:01:52 +00:00
void ProjectFileManager : : DiscardAutosave ( const FilePath & filename )
{
InvisibleTemporaryProject tempProject ;
auto & project = tempProject . Project ( ) ;
auto & projectFileManager = Get ( project ) ;
// Read the project, discarding autosave
projectFileManager . ReadProjectFile ( filename , true ) ;
2020-11-25 16:07:16 +00:00
if ( projectFileManager . mLastSavedTracks ) {
for ( auto wt : projectFileManager . mLastSavedTracks - > Any < WaveTrack > ( ) )
wt - > CloseLock ( ) ;
projectFileManager . mLastSavedTracks . reset ( ) ;
}
2020-11-19 18:01:52 +00:00
// Side-effect on database is done, and destructor of tempProject
// closes the temporary project properly
}
2019-06-09 14:25:01 +00:00
ProjectFileManager : : ProjectFileManager ( AudacityProject & project )
: mProject { project }
{
}
ProjectFileManager : : ~ ProjectFileManager ( ) = default ;
2019-12-07 16:56:24 +00:00
namespace {
const char * const defaultHelpUrl =
" FAQ:Errors_on_opening_or_recovering_an_Audacity_project " ;
using Pair = std : : pair < const char * , const char * > ;
const Pair helpURLTable [ ] = {
{
" not well-formed (invalid token) " ,
" Error:_not_well-formed_(invalid_token)_at_line_x "
} ,
{
" reference to invalid character number " ,
" Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x "
} ,
{
" mismatched tag " ,
" #mismatched "
} ,
2020-07-18 14:25:10 +00:00
// This error with FAQ entry is reported elsewhere, not here....
2019-12-07 16:56:24 +00:00
//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
} ;
wxString FindHelpUrl ( const TranslatableString & libraryError )
{
wxString helpUrl ;
if ( ! libraryError . empty ( ) ) {
helpUrl = defaultHelpUrl ;
auto msgid = libraryError . MSGID ( ) . GET ( ) ;
auto found = std : : find_if ( begin ( helpURLTable ) , end ( helpURLTable ) ,
[ & ] ( const Pair & pair ) {
return msgid . Contains ( pair . first ) ; }
) ;
if ( found ! = end ( helpURLTable ) ) {
auto url = found - > second ;
if ( url [ 0 ] = = ' # ' )
helpUrl + = url ;
else
helpUrl = url ;
}
}
return helpUrl ;
}
}
2020-11-19 18:01:52 +00:00
auto ProjectFileManager : : ReadProjectFile (
const FilePath & fileName , bool discardAutosave )
2019-06-09 14:25:01 +00:00
- > ReadProjectResults
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
auto & window = GetProjectFrame ( project ) ;
///
/// Parse project file
///
2020-11-19 18:01:52 +00:00
bool bParseSuccess = projectFileIO . LoadProject ( fileName , discardAutosave ) ;
2019-06-09 14:25:01 +00:00
bool err = false ;
2020-07-01 05:45:17 +00:00
if ( bParseSuccess )
{
2020-11-19 18:01:52 +00:00
if ( discardAutosave )
2021-01-23 19:00:36 +00:00
// REVIEW: Failure OK?
2020-11-19 18:01:52 +00:00
projectFileIO . AutoSaveDelete ( ) ;
else if ( projectFileIO . IsRecovered ( ) ) {
2020-07-22 16:41:03 +00:00
bool resaved = false ;
if ( ! projectFileIO . IsTemporary ( ) )
{
2020-12-07 01:50:31 +00:00
// Re-save non-temporary project to its own path. This
// might fail to update the document blob in the database.
resaved = projectFileIO . SaveProject ( fileName , nullptr ) ;
2020-07-22 16:41:03 +00:00
}
2020-07-01 18:26:20 +00:00
AudacityMessageBox (
2020-07-20 18:37:36 +00:00
resaved
? XO ( " This project was not saved properly the last time Audacity ran. \n \n "
2020-07-20 21:34:48 +00:00
" It has been recovered to the last snapshot. " )
2020-07-20 18:37:36 +00:00
: XO ( " This project was not saved properly the last time Audacity ran. \n \n "
2020-07-20 21:34:48 +00:00
" It has been recovered to the last snapshot, but you must save it \n "
" to preserve its contents. " ) ,
2020-07-01 18:26:20 +00:00
XO ( " Project Recovered " ) ,
wxICON_WARNING ,
& window ) ;
}
2019-06-09 14:25:01 +00:00
// 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().
2019-05-21 15:22:19 +00:00
mLastSavedTracks = TrackList : : Create ( nullptr ) ;
2019-06-09 14:25:01 +00:00
auto & tracks = TrackList : : Get ( project ) ;
2020-07-01 05:45:17 +00:00
for ( auto t : tracks . Any ( ) )
{
2019-06-09 14:25:01 +00:00
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 ( ) ) ;
}
}
2020-07-01 18:26:20 +00:00
return
{
bParseSuccess ,
err ,
projectFileIO . GetLastError ( ) ,
FindHelpUrl ( projectFileIO . GetLibraryError ( ) )
2019-12-07 16:56:24 +00:00
} ;
2019-06-09 14:25:01 +00:00
}
bool ProjectFileManager : : Save ( )
{
2020-07-01 05:45:17 +00:00
auto & projectFileIO = ProjectFileIO : : Get ( mProject ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
// Prompt for file name?
2020-07-01 18:26:20 +00:00
if ( projectFileIO . IsTemporary ( ) )
2020-07-01 05:45:17 +00:00
{
2021-02-19 01:35:11 +00:00
return SaveAs ( true ) ;
2020-07-01 05:45:17 +00:00
}
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
return DoSave ( projectFileIO . GetFileName ( ) , false ) ;
2019-06-09 14:25:01 +00:00
}
#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
2020-07-01 05:45:17 +00:00
// Assumes ProjectFileIO::mFileName has been set to the desired path.
bool ProjectFileManager : : DoSave ( const FilePath & fileName , const bool fromSaveAs )
2019-06-09 14:25:01 +00:00
{
// See explanation above
// ProjectDisabler disabler(this);
auto & proj = mProject ;
auto & window = GetProjectFrame ( proj ) ;
auto & projectFileIO = ProjectFileIO : : Get ( proj ) ;
const auto & settings = ProjectSettings : : Get ( proj ) ;
// Some confirmation dialogs
{
2021-01-14 15:50:06 +00:00
if ( TempDirectory : : FATFilesystemDenied ( fileName , XO ( " Projects cannot be saved to FAT drives. " ) ) )
2021-01-05 07:32:03 +00:00
{
return false ;
}
2019-06-09 14:25:01 +00:00
auto & tracks = TrackList : : Get ( proj ) ;
2020-07-01 05:45:17 +00:00
if ( ! tracks . Any ( ) )
2019-06-09 14:25:01 +00:00
{
2020-07-01 05:45:17 +00:00
if ( UndoManager : : Get ( proj ) . UnsavedChanges ( ) & &
settings . EmptyCanBeDirty ( ) )
{
2019-12-07 19:30:07 +00:00
int result = AudacityMessageBox (
XO (
2020-07-01 05:45:17 +00:00
" 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? " ) ,
2019-12-07 19:30:07 +00:00
XO ( " Warning - Empty Project " ) ,
wxYES_NO | wxICON_QUESTION ,
& window ) ;
2019-06-09 14:25:01 +00:00
if ( result = = wxNO )
2020-07-01 05:45:17 +00:00
{
2019-06-09 14:25:01 +00:00
return false ;
2020-07-01 05:45:17 +00:00
}
2019-06-09 14:25:01 +00:00
}
}
2021-01-11 07:04:16 +00:00
wxULongLong fileSize = wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) ;
wxDiskspaceSize_t freeSpace ;
if ( wxGetDiskSpace ( FileNames : : AbbreviatePath ( fileName ) , NULL , & freeSpace ) )
{
if ( freeSpace . GetValue ( ) < = fileSize . GetValue ( ) )
{
ShowErrorDialog (
& window ,
XO ( " Insufficient Disk Space " ) ,
XO ( " The project size exceeds the available free space on the target disk. \n \n "
" Please select a different disk with more free space. " ) ,
" Error:_Disk_full_or_not_writable "
) ;
return false ;
}
}
2019-06-09 14:25:01 +00:00
}
// End of confirmations
// Always save a backup of the original project file
2020-11-22 17:20:05 +00:00
Optional < ProjectFileIO : : BackupProject > pBackupProject ;
2020-07-06 23:55:53 +00:00
if ( fromSaveAs & & wxFileExists ( fileName ) )
2020-07-01 05:45:17 +00:00
{
2020-11-22 17:20:05 +00:00
pBackupProject . emplace ( projectFileIO , fileName ) ;
if ( ! pBackupProject - > IsOk ( ) )
2019-06-09 14:25:01 +00:00
return false ;
}
2021-01-03 22:02:43 +00:00
if ( FileNames : : IsOnFATFileSystem ( fileName ) )
{
if ( wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) > UINT32_MAX )
{
ShowErrorDialog (
& window ,
XO ( " Error Saving Project " ) ,
XO ( " The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem. " ) ,
2021-01-07 04:11:49 +00:00
" Error:_Unsuitable_drive "
2021-01-03 22:02:43 +00:00
) ;
return false ;
}
}
2020-11-18 21:34:21 +00:00
bool success = projectFileIO . SaveProject ( fileName , mLastSavedTracks . get ( ) ) ;
2020-07-01 05:45:17 +00:00
if ( ! success )
{
2020-12-07 03:14:54 +00:00
// Show this error only if we didn't fail reconnection in SaveProject
2021-01-23 19:00:36 +00:00
// REVIEW: Could HasConnection() be true but SaveProject() still have failed?
2021-01-04 01:22:22 +00:00
if ( ! projectFileIO . HasConnection ( ) )
2020-12-07 03:14:54 +00:00
ShowErrorDialog (
& window ,
XO ( " Error Saving Project " ) ,
2020-12-09 22:41:22 +00:00
FileException : : WriteFailureMessage ( fileName ) ,
2020-12-07 03:14:54 +00:00
" Error:_Disk_full_or_not_writable "
) ;
2019-06-09 14:25:01 +00:00
return false ;
}
2020-07-15 13:28:00 +00:00
proj . SetProjectName ( wxFileName ( fileName ) . GetName ( ) ) ;
projectFileIO . SetProjectTitle ( ) ;
2020-07-01 05:45:17 +00:00
UndoManager : : Get ( proj ) . StateSaved ( ) ;
ProjectStatus : : Get ( proj ) . Set ( XO ( " Saved %s " ) . Format ( fileName ) ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
if ( mLastSavedTracks )
{
mLastSavedTracks - > Clear ( ) ;
2019-06-09 14:25:01 +00:00
}
2020-07-01 05:45:17 +00:00
mLastSavedTracks = TrackList : : Create ( nullptr ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
auto & tracks = TrackList : : Get ( proj ) ;
for ( auto t : tracks . Any ( ) )
2019-06-09 14:25:01 +00:00
{
2020-07-01 05:45:17 +00:00
mLastSavedTracks - > Add ( t - > Duplicate ( ) ) ;
2019-06-09 14:25:01 +00:00
}
// If we get here, saving the project was successful, so we can DELETE
2020-11-22 17:20:05 +00:00
// any backup project.
if ( pBackupProject )
pBackupProject - > Discard ( ) ;
2019-06-09 14:25:01 +00:00
return true ;
}
2020-07-03 19:38:57 +00:00
// This version of SaveAs is invoked only from scripting and does not
// prompt for a file name
2020-07-03 22:12:26 +00:00
bool ProjectFileManager : : SaveAs ( const FilePath & newFileName , bool addToHistory /*= true*/ )
2019-06-09 14:25:01 +00:00
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
2020-07-01 05:45:17 +00:00
auto oldFileName = projectFileIO . GetFileName ( ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
bool bOwnsNewName = ! projectFileIO . IsTemporary ( ) & & ( oldFileName = = newFileName ) ;
2019-06-09 14:25:01 +00:00
//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.
2020-07-01 05:45:17 +00:00
if ( ! bOwnsNewName & & wxFileExists ( newFileName ) ) {
2019-06-09 14:25:01 +00:00
AudacityMessageDialog m (
2019-12-18 00:40:32 +00:00
nullptr ,
XO ( " The project was not saved because the file name provided would overwrite another project. \n Please try again and select an original name. " ) ,
XO ( " Error Saving Project " ) ,
wxOK | wxICON_ERROR ) ;
2019-06-09 14:25:01 +00:00
m . ShowModal ( ) ;
return false ;
}
2020-07-01 05:45:17 +00:00
auto success = DoSave ( newFileName , ! bOwnsNewName ) ;
2019-06-09 14:25:01 +00:00
if ( success & & addToHistory ) {
2020-07-01 05:45:17 +00:00
FileHistory : : Global ( ) . Append ( projectFileIO . GetFileName ( ) ) ;
2019-06-09 14:25:01 +00:00
}
return ( success ) ;
}
2021-02-19 01:35:11 +00:00
bool ProjectFileManager : : SaveAs ( bool allowOverwrite /* = false */ )
2019-06-09 14:25:01 +00:00
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
auto & window = GetProjectFrame ( project ) ;
TitleRestorer Restorer ( window , project ) ; // RAII
2020-07-01 18:26:20 +00:00
wxFileName filename ;
2020-07-27 19:11:50 +00:00
FilePath defaultSavePath = FileNames : : FindDefaultPath ( FileNames : : Operation : : Save ) ;
2020-07-01 18:26:20 +00:00
if ( projectFileIO . IsTemporary ( ) ) {
2020-07-27 19:11:50 +00:00
filename . SetPath ( defaultSavePath ) ;
2020-07-20 22:27:34 +00:00
filename . SetName ( project . GetProjectName ( ) ) ;
2020-07-01 18:26:20 +00:00
}
else {
filename = projectFileIO . GetFileName ( ) ;
2020-07-01 05:45:17 +00:00
}
2019-06-09 14:25:01 +00:00
2020-07-03 19:38:57 +00:00
// Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
2019-06-09 14:25:01 +00:00
if ( ! FileNames : : IsPathAvailable ( filename . GetPath ( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR ) ) ) {
2020-07-27 19:11:50 +00:00
filename . SetPath ( defaultSavePath ) ;
2019-06-09 14:25:01 +00:00
}
2020-07-01 05:45:17 +00:00
TranslatableString title = XO ( " %sSave Project \" %s \" As... " )
. Format ( Restorer . sProjNumber , Restorer . sProjName ) ;
TranslatableString message = XO ( " \
2019-06-09 14:25:01 +00:00
' 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 " );
2020-07-01 05:45:17 +00:00
2020-07-27 19:11:50 +00:00
if ( ShowWarningDialog ( & window , wxT ( " FirstProjectSave " ) , message , true ) ! = wxID_OK ) {
2019-06-09 14:25:01 +00:00
return false ;
}
2020-07-01 05:45:17 +00:00
bool bPrompt = ( project . mBatchMode = = 0 ) | | ( projectFileIO . GetFileName ( ) . empty ( ) ) ;
2020-07-03 19:38:57 +00:00
FilePath fName ;
2020-07-03 22:12:26 +00:00
bool bOwnsNewName ;
2019-06-09 14:25:01 +00:00
2020-07-03 22:12:26 +00:00
do {
if ( bPrompt ) {
// JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
// for overwrite ourselves later, and we disallow it.
2020-07-27 19:11:50 +00:00
fName = FileNames : : SelectFile ( FileNames : : Operation : : Save ,
2020-07-03 22:12:26 +00:00
title ,
filename . GetPath ( ) ,
filename . GetFullName ( ) ,
wxT ( " aup3 " ) ,
{ FileNames : : AudacityProjects } ,
wxFD_SAVE | wxRESIZE_BORDER ,
& window ) ;
2019-06-09 14:25:01 +00:00
2020-07-03 22:12:26 +00:00
if ( fName . empty ( ) )
return false ;
2019-06-09 14:25:01 +00:00
2020-07-03 22:12:26 +00:00
filename = fName ;
} ;
2019-06-09 14:25:01 +00:00
2020-07-03 22:12:26 +00:00
filename . SetExt ( wxT ( " aup3 " ) ) ;
2019-06-09 14:25:01 +00:00
2021-02-19 01:35:11 +00:00
if ( ( ! bPrompt | | ! allowOverwrite ) & & filename . FileExists ( ) ) {
2020-07-03 22:12:26 +00:00
// Saving a copy of the project should never overwrite an existing project.
2019-06-09 14:25:01 +00:00
AudacityMessageDialog m (
2019-12-18 00:40:32 +00:00
nullptr ,
2020-07-03 22:12:26 +00:00
XO ( " The project was not saved because the file name provided would overwrite another project. \n Please try again and select an original name. " ) ,
2019-12-18 00:40:32 +00:00
XO ( " Error Saving Project " ) ,
wxOK | wxICON_ERROR ) ;
2019-06-09 14:25:01 +00:00
m . ShowModal ( ) ;
return false ;
}
2020-07-03 22:12:26 +00:00
fName = filename . GetFullPath ( ) ;
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 ( ! 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 = ( projectFileIO . GetFileName ( ) = = fName ) ? 2 : 1 ;
for ( auto p : AllProjects { } ) {
const wxFileName openProjectName { ProjectFileIO : : Get ( * 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 . */
auto Message = XO ( " \
Do you want to overwrite the project : \ n \ " %s \" ? \n \n \
If you select \ " Yes \" the project \n \" %s \" \n \
will be irreversibly overwritten . " ).Format( 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.*/
XO ( " Overwrite Project Warning " ) ,
wxYES_NO | wxNO_DEFAULT | wxICON_WARNING ,
& window ) ;
if ( result = = wxNO ) {
continue ;
}
if ( result = = wxCANCEL ) {
return false ;
}
}
2020-07-27 19:11:50 +00:00
else {
2020-07-03 22:12:26 +00:00
// Overwrite disallowed. The destination project is open in another window.
AudacityMessageDialog m (
nullptr ,
XO ( " The project was not saved because the selected project is open in another window. \n Please try again and select an original name. " ) ,
XO ( " Error Saving Project " ) ,
wxOK | wxICON_ERROR ) ;
m . ShowModal ( ) ;
continue ;
}
}
break ;
} while ( bPrompt ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
auto success = DoSave ( fName , ! bOwnsNewName ) ;
2019-06-09 14:25:01 +00:00
if ( success ) {
2020-07-01 05:45:17 +00:00
FileHistory : : Global ( ) . Append ( projectFileIO . GetFileName ( ) ) ;
2019-06-09 14:25:01 +00:00
}
return ( success ) ;
}
2020-07-03 22:12:26 +00:00
bool ProjectFileManager : : SaveCopy ( const FilePath & fileName /* = wxT("") */ )
2020-07-03 19:38:57 +00:00
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
auto & window = GetProjectFrame ( project ) ;
TitleRestorer Restorer ( window , project ) ; // RAII
2020-07-03 22:12:26 +00:00
wxFileName filename = fileName ;
2020-07-27 19:11:50 +00:00
FilePath defaultSavePath = FileNames : : FindDefaultPath ( FileNames : : Operation : : Save ) ;
2020-07-03 19:38:57 +00:00
2020-07-03 22:12:26 +00:00
if ( fileName . empty ( ) )
2020-07-03 19:38:57 +00:00
{
2020-07-03 22:12:26 +00:00
if ( projectFileIO . IsTemporary ( ) )
{
2020-07-27 19:11:50 +00:00
filename . SetPath ( defaultSavePath ) ;
2020-07-03 22:12:26 +00:00
}
else
{
filename = projectFileIO . GetFileName ( ) ;
}
2020-07-03 19:38:57 +00:00
}
// Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
if ( ! FileNames : : IsPathAvailable ( filename . GetPath ( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR ) ) )
{
2020-07-27 19:11:50 +00:00
filename . SetPath ( defaultSavePath ) ;
2020-07-03 19:38:57 +00:00
}
TranslatableString title =
2020-07-03 22:12:26 +00:00
XO ( " %sSave Copy of Project \" %s \" As... " )
. Format ( Restorer . sProjNumber , Restorer . sProjName ) ;
2020-07-03 19:38:57 +00:00
bool bPrompt = ( project . mBatchMode = = 0 ) | | ( projectFileIO . GetFileName ( ) . empty ( ) ) ;
FilePath fName ;
do
{
if ( bPrompt )
{
// JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
// for overwrite ourselves later, and we disallow it.
2021-02-03 16:41:28 +00:00
// Previously we disallowed overwrite because we would have had
// to DELETE the many smaller files too, or prompt to move them.
// Maybe we could allow it now that we have aup3 format?
2020-07-03 19:38:57 +00:00
fName = FileNames : : SelectFile ( FileNames : : Operation : : Export ,
title ,
filename . GetPath ( ) ,
filename . GetFullName ( ) ,
wxT ( " aup3 " ) ,
{ FileNames : : AudacityProjects } ,
wxFD_SAVE | wxRESIZE_BORDER ,
& window ) ;
if ( fName . empty ( ) )
{
return false ;
}
filename = fName ;
} ;
filename . SetExt ( wxT ( " aup3 " ) ) ;
2021-01-14 15:50:06 +00:00
if ( TempDirectory : : FATFilesystemDenied ( filename . GetFullPath ( ) , XO ( " Projects cannot be saved to FAT drives. " ) ) )
2021-01-05 07:32:03 +00:00
{
if ( project . mBatchMode )
{
return false ;
}
continue ;
}
2020-07-03 22:12:26 +00:00
if ( filename . FileExists ( ) )
2020-07-03 19:38:57 +00:00
{
// Saving a copy of the project should never overwrite an existing project.
AudacityMessageDialog m ( nullptr ,
XO ( " Saving a copy must not overwrite an existing saved project. \n Please try again and select an original name. " ) ,
XO ( " Error Saving Copy of Project " ) ,
wxOK | wxICON_ERROR ) ;
m . ShowModal ( ) ;
2020-07-05 21:05:43 +00:00
if ( project . mBatchMode )
2020-07-03 22:12:26 +00:00
{
return false ;
}
2020-07-03 19:38:57 +00:00
continue ;
}
2021-01-11 07:04:16 +00:00
wxULongLong fileSize = wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) ;
wxDiskspaceSize_t freeSpace ;
if ( wxGetDiskSpace ( FileNames : : AbbreviatePath ( filename . GetFullPath ( ) ) , NULL , & freeSpace ) )
{
if ( freeSpace . GetValue ( ) < = fileSize . GetValue ( ) )
{
ShowErrorDialog (
& window ,
XO ( " Insufficient Disk Space " ) ,
XO ( " The project size exceeds the available free space on the target disk. \n \n "
" Please select a different disk with more free space. " ) ,
" Error:_Unsuitable_drive "
) ;
continue ;
}
}
2021-01-03 22:33:05 +00:00
if ( FileNames : : IsOnFATFileSystem ( filename . GetFullPath ( ) ) )
{
2021-01-11 07:04:16 +00:00
if ( fileSize > UINT32_MAX )
2021-01-03 22:33:05 +00:00
{
ShowErrorDialog (
& window ,
XO ( " Error Saving Project " ) ,
XO ( " The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem. " ) ,
2021-01-07 04:11:49 +00:00
" Error:_Unsuitable_drive "
2021-01-03 22:33:05 +00:00
) ;
if ( project . mBatchMode )
{
return false ;
}
continue ;
}
}
2020-07-03 22:12:26 +00:00
fName = filename . GetFullPath ( ) ;
2020-07-03 19:38:57 +00:00
break ;
} while ( bPrompt ) ;
2020-12-09 23:00:41 +00:00
if ( ! projectFileIO . SaveCopy ( fName ) )
2020-07-03 19:38:57 +00:00
{
2020-12-09 23:00:41 +00:00
auto msg = FileException : : WriteFailureMessage ( fName ) ;
2020-07-03 19:38:57 +00:00
AudacityMessageDialog m (
2020-12-09 23:00:41 +00:00
nullptr , msg , XO ( " Error Saving Project " ) , wxOK | wxICON_ERROR ) ;
2020-07-03 22:12:26 +00:00
2020-07-03 19:38:57 +00:00
m . ShowModal ( ) ;
2020-07-03 22:12:26 +00:00
2020-07-03 19:38:57 +00:00
return false ;
}
return true ;
}
2019-06-09 14:25:01 +00:00
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 ;
2020-07-01 05:45:17 +00:00
if ( ! projectFileIO . IsModified ( ) ) {
sOldFilename = projectFileIO . GetFileName ( ) ;
2019-06-09 14:25:01 +00:00
}
// 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 ;
}
2020-07-01 05:45:17 +00:00
auto success = DoSave ( sNewFileName , true ) ;
2019-06-09 14:25:01 +00:00
2020-07-01 05:45:17 +00:00
if ( success ) {
FileHistory : : Global ( ) . Append ( projectFileIO . GetFileName ( ) ) ;
2019-06-09 14:25:01 +00:00
}
2020-07-01 05:45:17 +00:00
return success ;
2019-06-09 14:25:01 +00:00
}
2020-08-28 18:03:35 +00:00
void ProjectFileManager : : CompactProjectOnClose ( )
2019-06-09 14:25:01 +00:00
{
2020-07-20 16:10:31 +00:00
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
2019-06-09 14:25:01 +00:00
// Lock all blocks in all tracks of the last saved version, so that
2020-07-20 22:58:22 +00:00
// the sample blocks aren't deleted from the database when we destroy the
// sample block objects in memory.
2020-07-01 05:45:17 +00:00
if ( mLastSavedTracks )
{
2019-06-09 14:25:01 +00:00
for ( auto wt : mLastSavedTracks - > Any < WaveTrack > ( ) )
2020-07-01 05:45:17 +00:00
{
2019-06-09 14:25:01 +00:00
wt - > CloseLock ( ) ;
2020-07-01 05:45:17 +00:00
}
2019-06-09 14:25:01 +00:00
2020-07-29 04:25:50 +00:00
// Attempt to compact the project
2020-11-18 21:34:21 +00:00
projectFileIO . Compact ( { mLastSavedTracks . get ( ) } ) ;
2020-08-28 18:03:35 +00:00
if ( ! projectFileIO . WasCompacted ( ) & &
UndoManager : : Get ( project ) . UnsavedChanges ( ) ) {
// If compaction failed, we must do some work in case of close
// without save. Don't leave the document blob from the last
// push of undo history, when that undo state may get purged
// with deletion of some new sample blocks.
2021-01-23 19:00:36 +00:00
// REVIEW: UpdateSaved() might fail too. Do we need to test
// for that and report it?
2020-11-18 21:34:21 +00:00
projectFileIO . UpdateSaved ( mLastSavedTracks . get ( ) ) ;
2020-08-28 18:03:35 +00:00
}
2020-07-20 16:10:31 +00:00
}
}
2020-07-17 21:17:42 +00:00
2020-07-25 23:47:30 +00:00
bool ProjectFileManager : : OpenProject ( )
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
return projectFileIO . OpenProject ( ) ;
}
2021-04-06 17:09:28 +00:00
bool ProjectFileManager : : OpenNewProject ( )
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
bool bOK = OpenProject ( ) ;
if ( ! bOK )
{
ShowErrorDialog (
nullptr ,
XO ( " Can't open new empty project " ) ,
XO ( " Error opening a new empty project " ) ,
" FAQ:Errors_opening_a_new_empty_project " ,
true ,
projectFileIO . GetLastLog ( ) ) ;
}
return bOK ;
}
2020-07-20 16:10:31 +00:00
void ProjectFileManager : : CloseProject ( )
{
auto & project = mProject ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
projectFileIO . CloseProject ( ) ;
2020-08-28 18:03:35 +00:00
// Blocks were locked in CompactProjectOnClose, so DELETE the data structure so that
2020-07-20 16:10:31 +00:00
// there's no memory leak.
if ( mLastSavedTracks )
{
2019-06-09 14:25:01 +00:00
mLastSavedTracks - > Clear ( ) ;
mLastSavedTracks . reset ( ) ;
}
}
2019-06-08 20:05:22 +00:00
// static method, can be called outside of a project
2020-07-27 19:11:50 +00:00
wxArrayString ProjectFileManager : : ShowOpenDialog ( FileNames : : Operation op ,
2019-12-20 18:02:31 +00:00
const FileNames : : FileType & extraType )
2019-06-08 20:05:22 +00:00
{
// Construct the filter
2019-12-20 18:02:31 +00:00
const auto fileTypes = Importer : : Get ( ) . GetFileTypes ( extraType ) ;
2019-06-08 20:05:22 +00:00
2019-12-20 18:02:31 +00:00
// Retrieve saved path
2020-07-27 19:11:50 +00:00
auto path = FileNames : : FindDefaultPath ( op ) ;
2019-06-08 20:05:22 +00:00
// Construct and display the file dialog
wxArrayString selected ;
2019-12-20 18:02:31 +00:00
FileDialogWrapper dlog ( nullptr ,
2019-12-20 17:13:39 +00:00
XO ( " Select one or more files " ) ,
path ,
wxT ( " " ) ,
2019-12-20 18:02:31 +00:00
fileTypes ,
2019-12-20 17:13:39 +00:00
wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER ) ;
2019-06-08 20:05:22 +00:00
2019-12-20 18:02:31 +00:00
dlog . SetFilterIndex ( Importer : : SelectDefaultOpenType ( fileTypes ) ) ;
2019-06-08 20:05:22 +00:00
int dialogResult = dlog . ShowModal ( ) ;
// Convert the filter index to type and save
2019-12-20 18:02:31 +00:00
auto index = dlog . GetFilterIndex ( ) ;
const auto & saveType = fileTypes [ index ] ;
2019-12-14 01:11:00 +00:00
2019-12-20 18:02:31 +00:00
Importer : : SetDefaultOpenType ( saveType ) ;
Importer : : SetLastOpenType ( saveType ) ;
2019-06-08 20:05:22 +00:00
if ( dialogResult = = wxID_OK ) {
// Return the selected files
dlog . GetPaths ( selected ) ;
2020-07-27 19:11:50 +00:00
// Remember the directory
FileNames : : UpdateDefaultPath ( op , : : wxPathOnly ( dlog . GetPath ( ) ) ) ;
2019-06-08 20:05:22 +00:00
}
2020-07-27 19:11:50 +00:00
2019-06-08 20:05:22 +00:00
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 ) {
2020-07-01 05:45:17 +00:00
return newProjPathName . SameAs ( wxFileNameWrapper { ProjectFileIO : : Get ( * ptr ) . GetFileName ( ) } ) ;
2019-06-08 20:05:22 +00:00
} ) ;
if ( iter ! = finish ) {
2019-12-07 19:30:07 +00:00
auto errMsg =
XO ( " %s is already open in another window. " )
. Format ( newProjPathName . GetName ( ) ) ;
wxLogError ( errMsg . Translation ( ) ) ; //Debug?
AudacityMessageBox (
errMsg ,
XO ( " Error Opening Project " ) ,
wxOK | wxCENTRE ) ;
2019-06-08 20:05:22 +00:00
return true ;
}
return false ;
}
// FIXME:? TRAP_ERR This should return a result that is checked.
// See comment in AudacityApp::MRUOpen().
2021-05-16 13:19:49 +00:00
AudacityProject * ProjectFileManager : : OpenFile (
const FilePath & fileNameArg , bool addtohistory )
2019-06-08 20:05:22 +00:00
{
auto & project = mProject ;
auto & window = ProjectWindow : : Get ( project ) ;
// On Win32, we may be given a short (DOS-compatible) file name on rare
2020-04-11 07:08:33 +00:00
// occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
2019-06-08 20:05:22 +00:00
// convert these to long file name first.
2021-01-22 07:05:22 +00:00
auto fileName = PlatformCompatibility : : GetLongFileName ( fileNameArg ) ;
2019-06-08 20:05:22 +00:00
2021-01-14 15:50:06 +00:00
if ( TempDirectory : : FATFilesystemDenied ( fileName ,
2021-01-11 18:24:14 +00:00
XO ( " Project resides on FAT formatted drive. \n "
" Copy it to another drive to open it. " ) ) )
{
2021-05-16 13:19:49 +00:00
return nullptr ;
2021-01-11 18:24:14 +00:00
}
2019-06-08 20:05:22 +00:00
// 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 ) )
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
2020-07-15 06:32:48 +00:00
// Data loss may occur if users mistakenly try to open ".aup3.bak" files
2019-06-08 20:05:22 +00:00
// left over from an unsuccessful save or by previous versions of Audacity.
// So we always refuse to open such files.
2020-07-15 06:32:48 +00:00
if ( fileName . Lower ( ) . EndsWith ( wxT ( " .aup3.bak " ) ) )
2019-06-08 20:05:22 +00:00
{
AudacityMessageBox (
2019-12-07 19:30:07 +00:00
XO (
" 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. " ) ,
XO ( " Warning - Backup File Detected " ) ,
wxOK | wxCENTRE ,
& window ) ;
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
}
if ( ! : : wxFileExists ( fileName ) ) {
AudacityMessageBox (
2019-12-07 19:30:07 +00:00
XO ( " Could not open file: %s " ) . Format ( fileName ) ,
XO ( " Error Opening File " ) ,
wxOK | wxCENTRE ,
& window ) ;
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
}
2021-05-16 13:19:49 +00:00
// Following block covers cases other than a project file:
2019-06-08 20:05:22 +00:00
{
wxFFile ff ( fileName , wxT ( " rb " ) ) ;
2020-07-01 05:45:17 +00:00
auto cleanup = finally ( [ & ]
{
if ( ff . IsOpened ( ) )
{
ff . Close ( ) ;
}
} ) ;
2019-06-08 20:05:22 +00:00
if ( ! ff . IsOpened ( ) ) {
AudacityMessageBox (
2019-12-07 19:30:07 +00:00
XO ( " Could not open file: %s " ) . Format ( fileName ) ,
XO ( " Error opening file " ) ,
wxOK | wxCENTRE ,
& window ) ;
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
}
2020-07-01 05:45:17 +00:00
char buf [ 7 ] ;
auto numRead = ff . Read ( buf , 6 ) ;
if ( numRead ! = 6 ) {
2019-12-07 19:30:07 +00:00
AudacityMessageBox (
XO ( " File may be invalid or corrupted: \n %s " ) . Format ( fileName ) ,
XO ( " Error Opening File or Project " ) ,
wxOK | wxCENTRE ,
& window ) ;
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
}
2020-07-01 05:45:17 +00:00
if ( wxStrncmp ( buf , " SQLite " , 6 ) ! = 0 )
{
2021-05-16 13:19:49 +00:00
// Not a database
2019-06-08 20:05:22 +00:00
# ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
2020-07-01 05:45:17 +00:00
// Is it a plug-in?
2021-05-16 13:19:49 +00:00
if ( PluginManager : : Get ( ) . DropFile ( fileName ) ) {
2020-07-01 05:45:17 +00:00
MenuCreator : : RebuildAllMenuBars ( ) ;
2021-05-16 13:19:49 +00:00
// Plug-in installation happened, not really opening of a file,
// so return null
return nullptr ;
2020-07-01 05:45:17 +00:00
}
2019-06-08 20:05:22 +00:00
# endif
# ifdef USE_MIDI
2021-05-16 13:19:49 +00:00
if ( FileNames : : IsMidi ( fileName ) ) {
// If this succeeds, indo history is incremented, and it also does
// ZoomAfterImport:
if ( DoImportMIDI ( project , fileName ) )
return & project ;
return nullptr ;
2020-07-01 05:45:17 +00:00
}
2019-06-08 20:05:22 +00:00
# endif
2021-05-16 13:19:49 +00:00
// Undo history is incremented inside this:
if ( Import ( fileName ) ) {
// Bug 2743: Don't zoom with lof.
if ( ! fileName . AfterLast ( ' . ' ) . IsSameAs ( wxT ( " lof " ) , false ) )
window . ZoomAfterImport ( nullptr ) ;
return & project ;
2020-07-01 05:45:17 +00:00
}
2021-05-16 13:19:49 +00:00
return nullptr ;
2020-07-01 05:45:17 +00:00
}
2019-06-08 20:05:22 +00:00
}
2021-05-16 13:19:49 +00:00
return OpenProjectFile ( fileName , addtohistory ) ;
}
AudacityProject * ProjectFileManager : : OpenProjectFile (
const FilePath & fileName , bool addtohistory )
{
auto & project = mProject ;
auto & history = ProjectHistory : : Get ( project ) ;
auto & tracks = TrackList : : Get ( project ) ;
auto & trackPanel = TrackPanel : : Get ( project ) ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
auto & window = ProjectWindow : : Get ( project ) ;
2019-06-08 20:05:22 +00:00
auto results = ReadProjectFile ( fileName ) ;
const bool bParseSuccess = results . parseSuccess ;
2019-12-07 16:56:24 +00:00
const auto & errorStr = results . errorString ;
2019-06-08 20:05:22 +00:00
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 ( ) ) ;
2020-02-14 12:02:21 +00:00
selectionManager . TT_SetAudioTimeFormat ( settings . GetAudioTimeFormat ( ) ) ;
2019-06-08 20:05:22 +00:00
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 ) ;
2020-07-20 22:58:22 +00:00
// ? Old rationale in this comment no longer applies in 3.0.0, with no
// more on-demand loading:
2019-06-08 20:05:22 +00:00
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 )
2020-05-01 17:07:06 +00:00
FileHistory : : Global ( ) . Append ( fileName ) ;
2019-06-08 20:05:22 +00:00
}
if ( bParseSuccess ) {
if ( projectFileIO . IsRecovered ( ) )
{
// PushState calls AutoSave(), so no longer need to do so here.
2019-12-08 17:11:31 +00:00
history . PushState ( XO ( " Project was recovered " ) , XO ( " Recover " ) ) ;
2019-06-08 20:05:22 +00:00
}
2021-05-16 13:19:49 +00:00
return & project ;
2019-06-08 20:05:22 +00:00
}
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);
2019-12-07 16:56:24 +00:00
wxLogError ( wxT ( " Could not parse file \" %s \" . \n Error: %s " ) , fileName , errorStr . Debug ( ) ) ;
2019-06-08 20:05:22 +00:00
2021-04-01 06:13:15 +00:00
projectFileIO . ShowError (
2019-06-08 20:05:22 +00:00
& window ,
2019-12-08 05:25:47 +00:00
XO ( " Error Opening Project " ) ,
2019-12-05 18:38:07 +00:00
errorStr ,
2019-12-07 16:56:24 +00:00
results . helpUrl ) ;
2021-05-16 13:19:49 +00:00
return nullptr ;
2019-06-08 20:05:22 +00:00
}
}
2020-09-01 12:51:24 +00:00
void
2019-06-08 20:05:22 +00:00
ProjectFileManager : : AddImportedTracks ( const FilePath & fileName ,
2020-07-01 05:45:17 +00:00
TrackHolders & & newTracks )
2020-09-01 12:51:24 +00:00
{
2019-06-08 20:05:22 +00:00
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
2020-07-01 05:45:17 +00:00
wxFileName fn ( fileName ) ;
2019-06-08 20:05:22 +00:00
bool initiallyEmpty = tracks . empty ( ) ;
double newRate = 0 ;
2020-07-01 05:45:17 +00:00
wxString trackNameBase = fn . GetName ( ) ;
2019-06-08 20:05:22 +00:00
int i = - 1 ;
2020-09-09 16:43:41 +00:00
2020-11-10 21:10:27 +00:00
// Fix the bug 2109.
2020-09-09 16:43:41 +00:00
// In case the project had soloed tracks before importing,
// all newly imported tracks are muted.
2020-11-10 21:10:27 +00:00
const bool projectHasSolo =
! ( tracks . Any < PlayableTrack > ( ) + & PlayableTrack : : GetSolo ) . empty ( ) ;
2020-09-09 16:43:41 +00:00
if ( projectHasSolo )
{
for ( auto & track : newTracks )
for ( auto & channel : track )
channel - > SetMute ( true ) ;
}
2019-06-08 20:05:22 +00:00
// 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 ( ) ;
} ) ;
}
// 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 ) ;
}
2019-12-08 17:11:31 +00:00
history . PushState ( XO ( " Imported '%s' " ) . Format ( fileName ) ,
XO ( " Import " ) ) ;
2019-06-08 20:05:22 +00:00
# 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
2020-04-11 07:08:33 +00:00
// allow the creation to complete. If this becomes a problem, it "might" be possible
2019-06-08 20:05:22 +00:00
// to queue a dummy event to trigger the DoZoomFit().
wxEventLoopBase : : GetActive ( ) - > YieldFor ( wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT ) ;
# endif
2020-07-01 05:45:17 +00:00
// 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 ( ) ) ;
2020-08-10 22:48:59 +00:00
project . SetInitialImportPath ( fn . GetPath ( ) ) ;
2019-06-08 20:05:22 +00:00
projectFileIO . SetProjectTitle ( ) ;
}
// Moved this call to higher levels to prevent flicker redrawing everything on each file.
// HandleResize();
}
2020-11-09 00:24:20 +00:00
namespace {
bool ImportProject ( AudacityProject & dest , const FilePath & fileName )
{
InvisibleTemporaryProject temp ;
auto & project = temp . Project ( ) ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
if ( ! projectFileIO . LoadProject ( fileName , false ) )
return false ;
auto & srcTracks = TrackList : : Get ( project ) ;
auto & destTracks = TrackList : : Get ( dest ) ;
for ( const Track * pTrack : srcTracks . Any ( ) ) {
auto destTrack = pTrack - > PasteInto ( dest ) ;
Track : : FinishCopy ( pTrack , destTrack . get ( ) ) ;
if ( destTrack . use_count ( ) = = 1 )
destTracks . Add ( destTrack ) ;
}
Tags : : Get ( dest ) . Merge ( Tags : : Get ( project ) ) ;
return true ;
}
}
2019-06-08 20:05:22 +00:00
// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
bool ProjectFileManager : : Import (
2020-08-10 22:48:59 +00:00
const FilePath & fileName ,
bool addToHistory /* = true */ )
2019-06-08 20:05:22 +00:00
{
auto & project = mProject ;
2020-07-18 01:46:08 +00:00
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
2019-06-08 20:05:22 +00:00
auto oldTags = Tags : : Get ( project ) . shared_from_this ( ) ;
2020-07-18 01:46:08 +00:00
bool initiallyEmpty = TrackList : : Get ( project ) . empty ( ) ;
2019-06-08 20:05:22 +00:00
TrackHolders newTracks ;
2019-12-13 15:49:57 +00:00
TranslatableString errorMessage ;
2019-06-08 20:05:22 +00:00
2021-02-03 16:41:28 +00:00
# ifdef EXPERIMENTAL_IMPORT_AUP3
2020-07-15 06:32:48 +00:00
// Handle AUP3 ("project") files directly
if ( fileName . AfterLast ( ' . ' ) . IsSameAs ( wxT ( " aup3 " ) , false ) ) {
2020-11-09 00:24:20 +00:00
if ( ImportProject ( project , fileName ) ) {
2020-07-15 06:32:48 +00:00
auto & history = ProjectHistory : : Get ( project ) ;
2020-07-18 01:46:08 +00:00
// If the project was clean and temporary (not permanently saved), then set
// the filename to the just imported path.
if ( initiallyEmpty & & projectFileIO . IsTemporary ( ) ) {
2020-08-10 22:48:59 +00:00
wxFileName fn ( fileName ) ;
project . SetProjectName ( fn . GetName ( ) ) ;
project . SetInitialImportPath ( fn . GetPath ( ) ) ;
2020-07-18 01:46:08 +00:00
projectFileIO . SetProjectTitle ( ) ;
}
2020-07-15 06:32:48 +00:00
history . PushState ( XO ( " Imported '%s' " ) . Format ( fileName ) , XO ( " Import " ) ) ;
2020-08-10 22:48:59 +00:00
if ( addToHistory ) {
FileHistory : : Global ( ) . Append ( fileName ) ;
}
2020-07-15 06:32:48 +00:00
}
2021-01-26 08:21:12 +00:00
else {
errorMessage = projectFileIO . GetLastError ( ) ;
if ( errorMessage . empty ( ) ) {
errorMessage = XO ( " Failed to import project " ) ;
}
// Additional help via a Help button links to the manual.
ShowErrorDialog ( & GetProjectFrame ( project ) , XO ( " Error Importing " ) ,
errorMessage , wxT ( " Importing_Audio " ) ) ;
}
2020-07-15 06:32:48 +00:00
return false ;
}
2021-02-03 16:41:28 +00:00
# endif
2020-07-15 06:32:48 +00:00
2019-06-08 20:05:22 +00:00
{
// 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 ) ;
2021-02-03 16:41:28 +00:00
# ifndef EXPERIMENTAL_IMPORT_AUP3
// Handle AUP3 ("project") files specially
if ( fileName . AfterLast ( ' . ' ) . IsSameAs ( wxT ( " aup3 " ) , false ) ) {
2021-02-03 18:50:52 +00:00
ShowErrorDialog ( & GetProjectFrame ( project ) , XO ( " Error Importing " ) ,
XO ( " Cannot import AUP3 format. Use File > Open instead " ) ,
wxT ( " File_Menu " ) ) ;
return false ;
2021-02-03 16:41:28 +00:00
}
# endif
2021-02-03 18:50:52 +00:00
bool success = Importer : : Get ( ) . Import ( project , fileName ,
2020-08-22 23:44:49 +00:00
& WaveTrackFactory : : Get ( project ) ,
2019-06-08 20:05:22 +00:00
newTracks ,
newTags . get ( ) ,
errorMessage ) ;
if ( ! errorMessage . empty ( ) ) {
// Error message derived from Importer::Import
// Additional help via a Help button links to the manual.
2019-12-08 05:25:47 +00:00
ShowErrorDialog ( & GetProjectFrame ( project ) , XO ( " Error Importing " ) ,
2019-12-05 18:38:07 +00:00
errorMessage , wxT ( " Importing_Audio " ) ) ;
2019-06-08 20:05:22 +00:00
}
if ( ! success )
return false ;
2020-08-10 22:48:59 +00:00
if ( addToHistory ) {
FileHistory : : Global ( ) . Append ( fileName ) ;
}
2019-06-08 20:05:22 +00:00
// 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 ;
}
2020-07-15 06:32:48 +00:00
// Handle AUP ("legacy project") files directly
2020-07-15 12:57:20 +00:00
if ( fileName . AfterLast ( ' . ' ) . IsSameAs ( wxT ( " aup " ) , false ) ) {
2020-07-18 01:46:08 +00:00
// If the project was clean and temporary (not permanently saved), then set
// the filename to the just imported path.
if ( initiallyEmpty & & projectFileIO . IsTemporary ( ) ) {
2020-08-10 22:48:59 +00:00
wxFileName fn ( fileName ) ;
project . SetProjectName ( fn . GetName ( ) ) ;
project . SetInitialImportPath ( fn . GetPath ( ) ) ;
2020-07-18 01:46:08 +00:00
projectFileIO . SetProjectTitle ( ) ;
}
2020-07-12 08:53:25 +00:00
auto & history = ProjectHistory : : Get ( project ) ;
history . PushState ( XO ( " Imported '%s' " ) . Format ( fileName ) , XO ( " Import " ) ) ;
2021-05-16 13:19:49 +00:00
return true ;
2020-07-12 08:53:25 +00:00
}
2019-06-08 20:05:22 +00:00
// PRL: Undo history is incremented inside this:
2020-09-01 12:51:24 +00:00
AddImportedTracks ( fileName , std : : move ( newTracks ) ) ;
2019-06-08 20:05:22 +00:00
return true ;
}
2020-11-17 22:09:41 +00:00
# include "Clipboard.h"
# include "ShuttleGui.h"
# include "widgets/HelpSystem.h"
// Compact dialog
namespace {
class CompactDialog : public wxDialogWrapper
{
public :
CompactDialog ( TranslatableString text )
: wxDialogWrapper ( nullptr , wxID_ANY , XO ( " Compact Project " ) )
{
ShuttleGui S ( this , eIsCreating ) ;
S . StartVerticalLay ( true ) ;
{
S . AddFixedText ( text , false , 500 ) ;
S . AddStandardButtons ( eYesButton | eNoButton | eHelpButton ) ;
}
S . EndVerticalLay ( ) ;
FindWindowById ( wxID_YES , this ) - > Bind ( wxEVT_BUTTON , & CompactDialog : : OnYes , this ) ;
FindWindowById ( wxID_NO , this ) - > Bind ( wxEVT_BUTTON , & CompactDialog : : OnNo , this ) ;
FindWindowById ( wxID_HELP , this ) - > Bind ( wxEVT_BUTTON , & CompactDialog : : OnGetURL , this ) ;
Layout ( ) ;
Fit ( ) ;
Center ( ) ;
}
void OnYes ( wxCommandEvent & WXUNUSED ( evt ) )
{
EndModal ( wxYES ) ;
}
void OnNo ( wxCommandEvent & WXUNUSED ( evt ) )
{
EndModal ( wxNO ) ;
}
void OnGetURL ( wxCommandEvent & WXUNUSED ( evt ) )
{
HelpSystem : : ShowHelp ( this , wxT ( " File_Menu:_Compact_Project " ) , true ) ;
}
} ;
}
void ProjectFileManager : : Compact ( )
{
auto & project = mProject ;
auto & undoManager = UndoManager : : Get ( project ) ;
auto & clipboard = Clipboard : : Get ( ) ;
auto & projectFileIO = ProjectFileIO : : Get ( project ) ;
bool isBatch = project . mBatchMode > 0 ;
// Purpose of this is to remove the -wal file.
projectFileIO . ReopenProject ( ) ;
2020-11-18 20:01:02 +00:00
auto savedState = undoManager . GetSavedState ( ) ;
2020-11-19 01:37:11 +00:00
const auto currentState = undoManager . GetCurrentState ( ) ;
2020-11-18 20:01:02 +00:00
if ( savedState < 0 ) {
undoManager . StateSaved ( ) ;
savedState = undoManager . GetSavedState ( ) ;
if ( savedState < 0 ) {
wxASSERT ( false ) ;
savedState = 0 ;
}
2020-11-17 22:09:41 +00:00
}
2020-11-19 01:37:11 +00:00
const auto least = std : : min < size_t > ( savedState , currentState ) ;
const auto greatest = std : : max < size_t > ( savedState , currentState ) ;
2020-11-18 20:01:02 +00:00
std : : vector < const TrackList * > trackLists ;
2020-11-21 18:55:12 +00:00
auto fn = [ & ] ( auto & elem ) {
trackLists . push_back ( elem . state . tracks . get ( ) ) ; } ;
undoManager . VisitStates ( fn , least , 1 + least ) ;
if ( least ! = greatest )
undoManager . VisitStates ( fn , greatest , 1 + greatest ) ;
2020-11-17 22:09:41 +00:00
int64_t total = projectFileIO . GetTotalUsage ( ) ;
2020-11-18 20:01:02 +00:00
int64_t used = projectFileIO . GetCurrentUsage ( trackLists ) ;
2020-11-17 22:09:41 +00:00
auto before = wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) ;
CompactDialog dlg (
XO ( " Compacting this project will free up disk space by removing unused bytes within the file. \n \n "
" There is %s of free disk space and this project is currently using %s. \n "
" \n "
" If you proceed, the current Undo/Redo History and clipboard contents will be discarded "
" and you will recover approximately %s of disk space. \n "
" \n "
" Do you want to continue? " )
. Format ( Internat : : FormatSize ( projectFileIO . GetFreeDiskSpace ( ) ) ,
Internat : : FormatSize ( before . GetValue ( ) ) ,
Internat : : FormatSize ( total - used ) ) ) ;
if ( isBatch | | dlg . ShowModal ( ) = = wxYES )
{
2020-11-19 01:37:11 +00:00
// We can remove redo states, if they are after the saved state.
undoManager . RemoveStates ( 1 + greatest , undoManager . GetNumStates ( ) ) ;
2020-11-17 22:09:41 +00:00
2020-11-21 18:55:12 +00:00
// We can remove all states between the current and the last saved.
if ( least < greatest )
undoManager . RemoveStates ( least + 1 , greatest ) ;
2020-11-19 01:37:11 +00:00
// We can remove all states before the current and the last saved.
undoManager . RemoveStates ( 0 , least ) ;
2020-11-17 22:09:41 +00:00
2020-11-17 22:13:17 +00:00
// And clear the clipboard, if needed
if ( & mProject = = clipboard . Project ( ) . lock ( ) . get ( ) )
clipboard . Clear ( ) ;
2020-11-17 22:09:41 +00:00
// Refresh the before space usage since it may have changed due to the
// above actions.
auto before = wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) ;
2020-11-18 20:01:02 +00:00
projectFileIO . Compact ( trackLists , true ) ;
2020-11-17 22:09:41 +00:00
auto after = wxFileName : : GetSize ( projectFileIO . GetFileName ( ) ) ;
if ( ! isBatch )
{
AudacityMessageBox (
XO ( " Compacting actually freed %s of disk space. " )
. Format ( Internat : : FormatSize ( ( before - after ) . GetValue ( ) ) ) ,
XO ( " Compact Project " ) ) ;
}
2020-11-25 18:31:04 +00:00
undoManager . RenameState ( undoManager . GetCurrentState ( ) ,
XO ( " Compacted project file " ) ,
XO ( " Compact " ) ) ;
2020-11-17 22:09:41 +00:00
}
}