Audacity 3.2.0
ProjectFileManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ProjectFileManager.cpp
6
7Paul Licameli split from AudacityProject.cpp
8
9**********************************************************************/
10
11#include "ProjectFileManager.h"
12
13#include <wx/crt.h> // for wxPrintf
14
15#if defined(__WXGTK__)
16#include <wx/evtloop.h>
17#endif
18
19#include "AnalyzedWaveClip.h"
20#include "AudacityMessageBox.h"
21#include "AudacityMirProject.h"
22#include "BasicUI.h"
23#include "ClipMirAudioReader.h"
24#include "CodeConversions.h"
25#include "Export.h"
26#include "HelpText.h"
27#include "Import.h"
28#include "ImportPlugin.h"
30#include "Legacy.h"
33#include "Project.h"
34#include "ProjectFileIO.h"
35#include "ProjectHistory.h"
37#include "ProjectRate.h"
39#include "ProjectSettings.h"
40#include "ProjectStatus.h"
42#include "ProjectWindow.h"
43#include "ProjectWindows.h"
44#include "RealtimeEffectList.h"
45#include "SelectFile.h"
46#include "SelectUtilities.h"
47#include "SelectionState.h"
48#include "Tags.h"
49#include "TempDirectory.h"
50#include "TempoChange.h"
51#include "TimeDisplayMode.h"
52#include "TrackFocus.h"
53#include "TrackPanel.h"
54#include "UndoTracks.h"
55#include "UserException.h"
56#include "ViewInfo.h"
57#include "WaveClip.h"
58#include "WaveTrack.h"
59#include "WaveTrackUtilities.h"
60#include "XMLFileReader.h"
63#include "widgets/FileHistory.h"
65#include "widgets/Warning.h"
66#include "wxFileNameWrapper.h"
67#include "wxPanelWrapper.h"
68
70
71#include <optional>
72#include <wx/frame.h>
73#include <wx/log.h>
74
76 []( AudacityProject &parent ){
77 auto result = std::make_shared< ProjectFileManager >( parent );
78 return result;
79 }
80};
81
83{
84 return project.AttachedObjects::Get< ProjectFileManager >( sFileManagerKey );
85}
86
88{
89 return Get( const_cast< AudacityProject & >( project ) );
90}
91
93{
94 InvisibleTemporaryProject tempProject;
95 auto &project = tempProject.Project();
96 auto &projectFileManager = Get(project);
97 // Read the project, discarding autosave
98 projectFileManager.ReadProjectFile(filename, true);
99
100 if (projectFileManager.mLastSavedTracks) {
101 for (auto wt : projectFileManager.mLastSavedTracks->Any<WaveTrack>())
103 projectFileManager.mLastSavedTracks.reset();
104 }
105
106 // Side-effect on database is done, and destructor of tempProject
107 // closes the temporary project properly
108}
109
111: mProject{ project }
112{
113}
114
116
117namespace {
118
119const char *const defaultHelpUrl =
120 "FAQ:Errors_on_opening_or_recovering_an_Audacity_project";
121
122using Pair = std::pair< const char *, const char * >;
124 {
125 "not well-formed (invalid token)",
126 "Error:_not_well-formed_(invalid_token)_at_line_x"
127 },
128 {
129 "reference to invalid character number",
130 "Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x"
131 },
132 {
133 "mismatched tag",
134 "#mismatched"
135 },
136// This error with FAQ entry is reported elsewhere, not here....
137//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
138};
139
140wxString FindHelpUrl( const TranslatableString &libraryError )
141{
142 wxString helpUrl;
143 if ( !libraryError.empty() ) {
144 helpUrl = defaultHelpUrl;
145
146 auto msgid = libraryError.MSGID().GET();
147 auto found = std::find_if( begin(helpURLTable), end(helpURLTable),
148 [&]( const Pair &pair ) {
149 return msgid.Contains( pair.first ); }
150 );
151 if (found != end(helpURLTable)) {
152 auto url = found->second;
153 if (url[0] == '#')
154 helpUrl += url;
155 else
156 helpUrl = url;
157 }
158 }
159
160 return helpUrl;
161}
162
163}
164
166 const FilePath &fileName, bool discardAutosave )
168{
169 auto &project = mProject;
170 auto &projectFileIO = ProjectFileIO::Get( project );
171 auto &window = GetProjectFrame( project );
172
176 auto parseResult = projectFileIO.LoadProject(fileName, discardAutosave);
177 const bool bParseSuccess = parseResult.has_value();
178
179 bool err = false;
180 std::optional<TranslatableString> linkTypeChangeReason;
181
182 TranslatableString otherError;
183
184 if (bParseSuccess)
185 {
187 // By making a duplicate set of pointers to the existing blocks
188 // on disk, we add one to their reference count, guaranteeing
189 // that their reference counts will never reach zero and thus
190 // the version saved on disk will be preserved until the
191 // user selects Save().
192 // Do this before FixTracks might delete zero-length clips!
193 mLastSavedTracks = TrackList::Create( nullptr );
194 WaveTrack *leader{};
195 for (auto pTrack : tracks.Any<WaveTrack>()) {
196 // A rare place where TrackList::Channels remains necessary, to visit
197 // the right channels of stereo tracks not yet "zipped", otherwise
198 // later, CloseLock() will be missed for some sample blocks and
199 // corrupt the project
200 for (const auto pChannel : TrackList::Channels(pTrack)) {
201 auto left = leader;
202 auto newTrack =
204 leader = left
205 ? nullptr // now visiting the right channel
206 : (pChannel->GetLinkType() == Track::LinkType::None)
207 ? nullptr // now visiting a mono channel
208 : static_cast<WaveTrack*>(newTrack.get())
209 // now visiting a left channel
210 ;
211 mLastSavedTracks->Add(newTrack);
212 if (left)
213 // Zip clips allowing misalignment -- this may be a legacy
214 // project. This duplicate track will NOT be used for normal
215 // editing, but only later to visit all the sample blocks that
216 // existed at last save time.
217 left->ZipClips(false);
218 }
219 }
220
221 FixTracks(
222 tracks,
223 // Keep at most one of the error messages
224 [&](const auto& errorMessage) { otherError = errorMessage; err = true; },
225 [&](const auto& unlinkReason) { linkTypeChangeReason = unlinkReason; });
226
227 if (!err) {
228 if(linkTypeChangeReason && !discardAutosave)
229 {
231//i18n-hint: Text of the message dialog that may appear on attempt
232//to open a project created by Audacity version prior to 3.4.
233//%s will be replaced with an explanation of the actual reason of
234//project modification.
235"%s\n"
236"This feature is not supported in Audacity versions past 3.3.3.\n"
237"These stereo tracks have been split into mono tracks.\n"
238"As a result, some realtime effects may be missing.\n"
239"Please verify that everything works as intended before saving."
240 ).Format(linkTypeChangeReason->Translation()));
241 }
242
243 parseResult->Commit();
244 if (discardAutosave)
245 // REVIEW: Failure OK?
246 projectFileIO.AutoSaveDelete();
247 else if (projectFileIO.IsRecovered()) {
248 bool resaved = false;
249
250 if (!projectFileIO.IsTemporary() &&
251 !linkTypeChangeReason)
252 {
253 // Re-save non-temporary project to its own path. This
254 // might fail to update the document blob in the database.
255 resaved = projectFileIO.SaveProject(fileName, nullptr);
256 }
257
259 resaved
260 ? XO(
261"This project was not saved properly the last time Audacity ran.\n\n"
262"It has been recovered to the last snapshot.")
263 : XO(
264"This project was not saved properly the last time Audacity ran.\n\n"
265"It has been recovered to the last snapshot, but you must save it\n"
266"to preserve its contents."),
267 XO("Project Recovered"),
268 wxICON_WARNING,
269 &window);
270 }
271 }
272
274 }
275
276 return {
277 bParseSuccess,
278 err,
279 (bParseSuccess ? otherError : projectFileIO.GetLastError()),
280 FindHelpUrl(projectFileIO.GetLibraryError())
281 };
282}
283
285{
286 auto &projectFileIO = ProjectFileIO::Get(mProject);
287
289 mProject, [this](auto& path, bool rename)
290 { return DoSave(audacity::ToWXString(path), rename); });
291 action != OnSaveAction::Continue)
292 return action == OnSaveAction::Handled;
293
294 // Prompt for file name?
295 if (projectFileIO.IsTemporary())
296 {
297 return SaveAs(true);
298 }
299
300 return DoSave(projectFileIO.GetFileName(), false);
301}
302
303#if 0
304// I added this to "fix" bug #334. At that time, we were on wxWidgets 2.8.12 and
305// there was a window between the closing of the "Save" progress dialog and the
306// end of the actual save where the user was able to close the project window and
307// recursively enter the Save code (where they could inadvertently cause the issue
308// described in #334).
309//
310// When we converted to wx3, this "disabler" caused focus problems when returning
311// to the project after the save (bug #1172) because the focus and activate events
312// weren't being dispatched and the focus would get lost.
313//
314// After some testing, it looks like the window described above no longer exists,
315// so I've disabled the disabler. However, I'm leaving it here in case we run
316// into the problem in the future. (even though it can't be used as-is)
317class ProjectDisabler
318{
319public:
320 ProjectDisabler(wxWindow *w)
321 : mWindow(w)
322 {
323 mWindow->GetEventHandler()->SetEvtHandlerEnabled(false);
324 }
325 ~ProjectDisabler()
326 {
327 mWindow->GetEventHandler()->SetEvtHandlerEnabled(true);
328 }
329private:
330 wxWindow *mWindow;
331};
332#endif
333
334// Assumes ProjectFileIO::mFileName has been set to the desired path.
335bool ProjectFileManager::DoSave(const FilePath & fileName, const bool fromSaveAs)
336{
337 // See explanation above
338 // ProjectDisabler disabler(this);
339 auto &proj = mProject;
340 auto &window = GetProjectFrame( proj );
341 auto &projectFileIO = ProjectFileIO::Get( proj );
342 const auto &settings = ProjectSettings::Get( proj );
343
344 // Some confirmation dialogs
345 {
346 if (TempDirectory::FATFilesystemDenied(fileName, XO("Projects cannot be saved to FAT drives.")))
347 {
348 return false;
349 }
350
351 wxULongLong fileSize = wxFileName::GetSize(projectFileIO.GetFileName());
352
353 wxDiskspaceSize_t freeSpace;
354 if (wxGetDiskSpace(FileNames::AbbreviatePath(fileName), NULL, &freeSpace))
355 {
356 if (freeSpace.GetValue() <= fileSize.GetValue())
357 {
359 XO("Insufficient Disk Space"),
360 XO("The project size exceeds the available free space on the target disk.\n\n"
361 "Please select a different disk with more free space."),
362 "Error:_Disk_full_or_not_writable"
363 );
364
365 return false;
366 }
367 }
368 }
369 // End of confirmations
370
371 // Always save a backup of the original project file
372 std::optional<ProjectFileIO::BackupProject> pBackupProject;
373 if (fromSaveAs && wxFileExists(fileName))
374 {
375 pBackupProject.emplace(projectFileIO, fileName);
376 if (!pBackupProject->IsOk())
377 return false;
378 }
379
380 if (FileNames::IsOnFATFileSystem(fileName))
381 {
382 if (wxFileName::GetSize(projectFileIO.GetFileName()) > UINT32_MAX)
383 {
385 XO("Error Saving Project"),
386 XO("The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem."),
387 "Error:_Unsuitable_drive"
388 );
389 return false;
390 }
391 }
392
393 bool success = projectFileIO.SaveProject(fileName, mLastSavedTracks.get());
394 if (!success)
395 {
396 // Show this error only if we didn't fail reconnection in SaveProject
397 // REVIEW: Could HasConnection() be true but SaveProject() still have failed?
398 if (!projectFileIO.HasConnection()) {
399 using namespace BasicUI;
401 XO("Error Saving Project"),
403 "Error:_Disk_full_or_not_writable",
404 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
405 }
406 return false;
407 }
408
409 proj.SetProjectName(wxFileName(fileName).GetName());
410 projectFileIO.SetProjectTitle();
411
413 ProjectStatus::Get(proj).Set(XO("Saved %s").Format(fileName));
414
416 {
417 mLastSavedTracks->Clear();
418 }
420
421 auto &tracks = TrackList::Get(proj);
422 for (auto t : tracks)
423 mLastSavedTracks->Add(t->Duplicate(Track::DuplicateOptions{}.Backup()));
424
425 // If we get here, saving the project was successful, so we can DELETE
426 // any backup project.
427 if (pBackupProject)
428 pBackupProject->Discard();
429
430 return true;
431}
432
433// This version of SaveAs is invoked only from scripting and does not
434// prompt for a file name
435bool ProjectFileManager::SaveAs(const FilePath &newFileName, bool addToHistory /*= true*/)
436{
437 auto &project = mProject;
438 auto &projectFileIO = ProjectFileIO::Get( project );
439
440 auto oldFileName = projectFileIO.GetFileName();
441
442 bool bOwnsNewName = !projectFileIO.IsTemporary() && (oldFileName == newFileName);
443 //check to see if the NEW project file already exists.
444 //We should only overwrite it if this project already has the same name, where the user
445 //simply chose to use the save as command although the save command would have the effect.
446 if( !bOwnsNewName && wxFileExists(newFileName)) {
448 nullptr,
449 XO("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."),
450 XO("Error Saving Project"),
451 wxOK|wxICON_ERROR );
452 m.ShowModal();
453 return false;
454 }
455
456 auto success = DoSave(newFileName, !bOwnsNewName);
457 if (success && addToHistory) {
458 FileHistory::Global().Append( projectFileIO.GetFileName() );
459 }
460
461 return(success);
462}
463
464bool ProjectFileManager::SaveAs(bool allowOverwrite /* = false */)
465{
466 auto &project = mProject;
467 auto &projectFileIO = ProjectFileIO::Get( project );
468 auto &window = GetProjectFrame( project );
469 TitleRestorer Restorer( window, project ); // RAII
470 wxFileName filename;
471 FilePath defaultSavePath = FileNames::FindDefaultPath(FileNames::Operation::Save);
472
473 if (projectFileIO.IsTemporary()) {
474 filename.SetPath(defaultSavePath);
475 filename.SetName(project.GetProjectName());
476 }
477 else {
478 filename = projectFileIO.GetFileName();
479 }
480
481 // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
482 if( !FileNames::IsPathAvailable( filename.GetPath( wxPATH_GET_VOLUME| wxPATH_GET_SEPARATOR) ) ){
483 filename.SetPath(defaultSavePath);
484 }
485
486 TranslatableString title = XO("%sSave Project \"%s\" As...")
487 .Format( Restorer.sProjNumber, Restorer.sProjName );
488
489 bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty());
490 FilePath fName;
491 bool bOwnsNewName;
492
493 do {
494 if (bPrompt) {
495 // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
496 // for overwrite ourselves later, and we disallow it.
497 fName = SelectFile(FileNames::Operation::Save,
498 title,
499 filename.GetPath(),
500 filename.GetFullName(),
501 wxT("aup3"),
503 wxFD_SAVE | wxRESIZE_BORDER,
504 &window);
505
506 if (fName.empty())
507 return false;
508
509 filename = fName;
510 };
511
512 filename.SetExt(wxT("aup3"));
513
514 if ((!bPrompt || !allowOverwrite) && filename.FileExists()) {
515 // Saving a copy of the project should never overwrite an existing project.
517 nullptr,
518 XO("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."),
519 XO("Error Saving Project"),
520 wxOK|wxICON_ERROR );
521 m.ShowModal();
522 return false;
523 }
524
525 fName = filename.GetFullPath();
526
527 bOwnsNewName = !projectFileIO.IsTemporary() && ( projectFileIO.GetFileName() == fName );
528 // Check to see if the project file already exists, and if it does
529 // check that the project file 'belongs' to this project.
530 // otherwise, prompt the user before overwriting.
531 if (!bOwnsNewName && filename.FileExists()) {
532 // Ensure that project of same name is not open in another window.
533 // fName is the destination file.
534 // mFileName is this project.
535 // It is possible for mFileName == fName even when this project is not
536 // saved to disk, and we then need to check the destination file is not
537 // open in another window.
538 int mayOverwrite = ( projectFileIO.GetFileName() == fName ) ? 2 : 1;
539 for ( auto p : AllProjects{} ) {
540 const wxFileName openProjectName{ ProjectFileIO::Get(*p).GetFileName() };
541 if (openProjectName.SameAs(fName)) {
542 mayOverwrite -= 1;
543 if (mayOverwrite == 0)
544 break;
545 }
546 }
547
548 if (mayOverwrite > 0) {
549 /* i18n-hint: In each case, %s is the name
550 of the file being overwritten.*/
551 auto Message = XO("\
552 Do you want to overwrite the project:\n\"%s\"?\n\n\
553 If you select \"Yes\" the project\n\"%s\"\n\
554 will be irreversibly overwritten.").Format( fName, fName );
555
556 // For safety, there should NOT be an option to hide this warning.
557 int result = AudacityMessageBox(
558 Message,
559 /* i18n-hint: Heading: A warning that a project is about to be overwritten.*/
560 XO("Overwrite Project Warning"),
561 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING,
562 &window);
563 if (result == wxNO) {
564 continue;
565 }
566 if (result == wxCANCEL) {
567 return false;
568 }
569 }
570 else {
571 // Overwrite disallowed. The destination project is open in another window.
573 nullptr,
574 XO("The project was not saved because the selected project is open in another window.\nPlease try again and select an original name."),
575 XO("Error Saving Project"),
576 wxOK|wxICON_ERROR );
577 m.ShowModal();
578 continue;
579 }
580 }
581
582 break;
583 } while (bPrompt);
584
585
586 // Pretend that we are closing the project
587 if (!bOwnsNewName)
588 {
589 if (
592 return false;
593 }
594
595 auto success = DoSave(fName, !bOwnsNewName);
596 if (success) {
597 FileHistory::Global().Append( projectFileIO.GetFileName() );
598 }
599
600 return(success);
601}
602
603bool ProjectFileManager::SaveCopy(const FilePath &fileName /* = wxT("") */)
604{
605 auto &project = mProject;
606 auto &projectFileIO = ProjectFileIO::Get(project);
607 auto &window = GetProjectFrame(project);
608 TitleRestorer Restorer(window, project); // RAII
609 wxFileName filename = fileName;
610 FilePath defaultSavePath = FileNames::FindDefaultPath(FileNames::Operation::Save);
611
612 if (fileName.empty())
613 {
614 if (projectFileIO.IsTemporary())
615 {
616 filename.SetPath(defaultSavePath);
617 }
618 else
619 {
620 filename = projectFileIO.GetFileName();
621 }
622 }
623
624 // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
625 if (!FileNames::IsPathAvailable(filename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR)))
626 {
627 filename.SetPath(defaultSavePath);
628 }
629
631 XO("%sSave Copy of Project \"%s\" As...")
632 .Format(Restorer.sProjNumber, Restorer.sProjName);
633
634 bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty());
635 FilePath fName;
636
637 do
638 {
639 if (bPrompt)
640 {
641 // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
642 // for overwrite ourselves later, and we disallow it.
643 // Previously we disallowed overwrite because we would have had
644 // to DELETE the many smaller files too, or prompt to move them.
645 // Maybe we could allow it now that we have aup3 format?
646 fName = SelectFile(FileNames::Operation::Export,
647 title,
648 filename.GetPath(),
649 filename.GetFullName(),
650 wxT("aup3"),
652 wxFD_SAVE | wxRESIZE_BORDER,
653 &window);
654
655 if (fName.empty())
656 {
657 return false;
658 }
659
660 filename = fName;
661 };
662
663 filename.SetExt(wxT("aup3"));
664
665 if (TempDirectory::FATFilesystemDenied(filename.GetFullPath(), XO("Projects cannot be saved to FAT drives.")))
666 {
667 if (project.mBatchMode)
668 {
669 return false;
670 }
671
672 continue;
673 }
674
675 if (filename.FileExists())
676 {
677 // Saving a copy of the project should never overwrite an existing project.
678 AudacityMessageDialog m(nullptr,
679 XO("Saving a copy must not overwrite an existing saved project.\nPlease try again and select an original name."),
680 XO("Error Saving Copy of Project"),
681 wxOK | wxICON_ERROR);
682 m.ShowModal();
683
684 if (project.mBatchMode)
685 {
686 return false;
687 }
688
689 continue;
690 }
691
692 wxULongLong fileSize = wxFileName::GetSize(projectFileIO.GetFileName());
693
694 wxDiskspaceSize_t freeSpace;
695 if (wxGetDiskSpace(FileNames::AbbreviatePath(filename.GetFullPath()), NULL, &freeSpace))
696 {
697 if (freeSpace.GetValue() <= fileSize.GetValue())
698 {
700 XO("Insufficient Disk Space"),
701 XO("The project size exceeds the available free space on the target disk.\n\n"
702 "Please select a different disk with more free space."),
703 "Error:_Unsuitable_drive"
704 );
705
706 continue;
707 }
708 }
709
710 if (FileNames::IsOnFATFileSystem(filename.GetFullPath()))
711 {
712 if (fileSize > UINT32_MAX)
713 {
715 XO("Error Saving Project"),
716 XO("The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem."),
717 "Error:_Unsuitable_drive"
718 );
719
720 if (project.mBatchMode)
721 {
722 return false;
723 }
724
725 continue;
726 }
727 }
728
729 fName = filename.GetFullPath();
730 break;
731 } while (bPrompt);
732
733 if (!projectFileIO.SaveCopy(fName))
734 {
735 auto msg = FileException::WriteFailureMessage(fName);
737 nullptr, msg, XO("Error Saving Project"), wxOK | wxICON_ERROR);
738
739 m.ShowModal();
740
741 return false;
742 }
743
744 return true;
745}
746
748{
749 auto &project = mProject;
750 auto &projectFileIO = ProjectFileIO::Get( project );
751
752 // MY: Will save the project to a NEW location a-la Save As
753 // and then tidy up after itself.
754
755 wxString sNewFileName = fnFile.GetFullPath();
756
757 // MY: To allow SaveAs from Timer Recording we need to check what
758 // the value of mFileName is before we change it.
759 FilePath sOldFilename;
760 if (!projectFileIO.IsModified()) {
761 sOldFilename = projectFileIO.GetFileName();
762 }
763
764 // MY: If the project file already exists then bail out
765 // and send populate the message string (pointer) so
766 // we can tell the user what went wrong.
767 if (wxFileExists(sNewFileName)) {
768 return false;
769 }
770
771 auto success = DoSave(sNewFileName, true);
772
773 if (success) {
774 FileHistory::Global().Append( projectFileIO.GetFileName() );
775 }
776
777 return success;
778}
779
781{
782 auto &project = mProject;
783 auto &projectFileIO = ProjectFileIO::Get(project);
784
785 // Lock all blocks in all tracks of the last saved version, so that
786 // the sample blocks aren't deleted from the database when we destroy the
787 // sample block objects in memory.
789 {
790 for (auto wt : mLastSavedTracks->Any<WaveTrack>())
792
793 // Attempt to compact the project
794 projectFileIO.Compact( { mLastSavedTracks.get() } );
795
796 if ( !projectFileIO.WasCompacted() &&
798 // If compaction failed, we must do some work in case of close
799 // without save. Don't leave the document blob from the last
800 // push of undo history, when that undo state may get purged
801 // with deletion of some new sample blocks.
802 // REVIEW: UpdateSaved() might fail too. Do we need to test
803 // for that and report it?
804 projectFileIO.UpdateSaved( mLastSavedTracks.get() );
805 }
806 }
807}
808
810{
811 auto &project = mProject;
812 auto &projectFileIO = ProjectFileIO::Get(project);
813
814 return projectFileIO.OpenProject();
815}
816
818{
819 auto &project = mProject;
820 auto &projectFileIO = ProjectFileIO::Get(project);
821
822 bool bOK = OpenProject();
823 if( !bOK )
824 {
825 auto tmpdir = wxFileName(TempDirectory::UnsavedProjectFileName()).GetPath();
826
827 UnwritableLocationErrorDialog dlg(nullptr, tmpdir);
828 dlg.ShowModal();
829 }
830 return bOK;
831}
832
834{
835 auto &project = mProject;
836 auto &projectFileIO = ProjectFileIO::Get(project);
837
838 projectFileIO.CloseProject();
839
840 // Blocks were locked in CompactProjectOnClose, so DELETE the data structure so that
841 // there's no memory leak.
843 {
844 mLastSavedTracks->Clear();
845 mLastSavedTracks.reset();
846 }
847}
848
849// static method, can be called outside of a project
851 const FileNames::FileType &extraType )
852{
853 // Construct the filter
854 const auto fileTypes = Importer::Get().GetFileTypes( extraType );
855
856 // Retrieve saved path
857 auto path = FileNames::FindDefaultPath(op);
858
859 // Construct and display the file dialog
860 wxArrayString selected;
861
862 FileDialogWrapper dlog(nullptr,
863 XO("Select one or more files"),
864 path,
865 wxT(""),
866 fileTypes,
867 wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
868
870
871 int dialogResult = dlog.ShowModal();
872
873 // Convert the filter index to type and save
874 auto index = dlog.GetFilterIndex();
875 const auto &saveType = fileTypes[ index ];
876
878 Importer::SetLastOpenType( saveType );
879
880 if (dialogResult == wxID_OK) {
881 // Return the selected files
882 dlog.GetPaths(selected);
883
884 // Remember the directory
885 FileNames::UpdateDefaultPath(op, ::wxPathOnly(dlog.GetPath()));
886 }
887
888 return selected;
889}
890
891// static method, can be called outside of a project
893{
894 const wxFileName newProjPathName(projPathName);
895 auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
896 iter = std::find_if( start, finish,
897 [&]( const AllProjects::value_type &ptr ){
898 return newProjPathName.SameAs(wxFileNameWrapper{ ProjectFileIO::Get(*ptr).GetFileName() });
899 } );
900 if (iter != finish) {
901 auto errMsg =
902 XO("%s is already open in another window.")
903 .Format( newProjPathName.GetName() );
904 wxLogError(errMsg.Translation()); //Debug?
906 errMsg,
907 XO("Error Opening Project"),
908 wxOK | wxCENTRE);
909 return true;
910 }
911 return false;
912}
913
915 const FilePath &fileNameArg, bool addtohistory)
916{
917 // On Win32, we may be given a short (DOS-compatible) file name on rare
918 // occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
919 // convert these to long file name first.
920 auto fileName = PlatformCompatibility::GetLongFileName(fileNameArg);
921
922 // Make sure it isn't already open.
923 // Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
924 // and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
925 // from, e.g., Win Explorer, it would bypass those, get to here with no check,
926 // then open a NEW project from the same data with no warning.
927 // This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
928 // but is not really part of that bug. Anyway, prevent it!
929 if (IsAlreadyOpen(fileName))
930 return nullptr;
931
932 // Data loss may occur if users mistakenly try to open ".aup3.bak" files
933 // left over from an unsuccessful save or by previous versions of Audacity.
934 // So we always refuse to open such files.
935 if (fileName.Lower().EndsWith(wxT(".aup3.bak")))
936 {
938 XO(
939"You are trying to open an automatically created backup file.\nDoing this may result in severe data loss.\n\nPlease open the actual Audacity project file instead."),
940 XO("Warning - Backup File Detected"),
941 wxOK | wxCENTRE,
942 nullptr);
943 return nullptr;
944 }
945
946 if (!::wxFileExists(fileName)) {
948 XO("Could not open file: %s").Format( fileName ),
949 XO("Error Opening File"),
950 wxOK | wxCENTRE,
951 nullptr);
952 return nullptr;
953 }
954
955 // Following block covers cases other than a project file:
956 {
957 wxFFile ff(fileName, wxT("rb"));
958
959 auto cleanup = finally([&]
960 {
961 if (ff.IsOpened())
962 {
963 ff.Close();
964 }
965 });
966
967 if (!ff.IsOpened()) {
969 XO("Could not open file: %s").Format( fileName ),
970 XO("Error opening file"),
971 wxOK | wxCENTRE,
972 nullptr);
973 return nullptr;
974 }
975
976 char buf[7];
977 auto numRead = ff.Read(buf, 6);
978 if (numRead != 6) {
980 XO("File may be invalid or corrupted: \n%s").Format( fileName ),
981 XO("Error Opening File or Project"),
982 wxOK | wxCENTRE,
983 nullptr);
984 return nullptr;
985 }
986
987 if (wxStrncmp(buf, "SQLite", 6) != 0)
988 {
989 // Not a database
990#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
991 // Is it a plug-in?
992 if (PluginManager::Get().DropFile(fileName)) {
994 // Plug-in installation happened, not really opening of a file,
995 // so return null
996 return nullptr;
997 }
998#endif
999 auto &project = chooser(false);
1000 // Undo history is incremented inside this:
1001 if (Get(project).Import(fileName))
1002 {
1003 // Undo history is incremented inside this:
1004 // Bug 2743: Don't zoom with lof.
1005 if (!fileName.AfterLast('.').IsSameAs(wxT("lof"), false))
1007 return &project;
1008 }
1009 return nullptr;
1010 }
1011 }
1012
1013 // Disallow opening of .aup3 project files from FAT drives, but only such
1014 // files, not importable types. (Bug 2800)
1016 XO("Project resides on FAT formatted drive.\n"
1017 "Copy it to another drive to open it.")))
1018 {
1019 return nullptr;
1020 }
1021
1022 auto &project = chooser(true);
1023 return Get(project).OpenProjectFile(fileName, addtohistory);
1024}
1025
1027 const std::function<void(const TranslatableString&)>& onError,
1028 const std::function<void(const TranslatableString&)>& onUnlink)
1029{
1030 // This is successively assigned the left member of each pair that
1031 // becomes unlinked
1032 Track::Holder unlinkedTrack;
1033 // Beware iterator invalidation, because stereo channels get zipped,
1034 // replacing WaveTracks
1035 for (auto iter = tracks.begin(); iter != tracks.end();) {
1036 auto t = (*iter++)->SharedPointer();
1037 const auto linkType = t->GetLinkType();
1038 // Note, the next function may have an important upgrading side effect,
1039 // and return no error; or it may find a real error and repair it, but
1040 // that repaired track won't be used because opening will fail.
1041 if (!t->LinkConsistencyFix()) {
1042 onError(XO("A channel of a stereo track was missing."));
1043 unlinkedTrack = nullptr;
1044 }
1045 if (!unlinkedTrack) {
1046 if (linkType != ChannelGroup::LinkType::None &&
1047 t->NChannels() == 1) {
1048 // The track became unlinked.
1049 // It should NOT have been replaced with a "zip"
1050 assert(t->GetOwner().get() == &tracks);
1051 // Wait until LinkConsistencyFix is called on the second track
1052 unlinkedTrack = t;
1053 // Fix the iterator, which skipped the right channel before the
1054 // unlinking
1055 iter = tracks.Find(t.get());
1056 ++iter;
1057 }
1058 }
1059 else {
1060 //Not an elegant way to deal with stereo wave track linking
1061 //compatibility between versions
1062 if (const auto left = dynamic_cast<WaveTrack*>(unlinkedTrack.get())) {
1063 if (const auto right = dynamic_cast<WaveTrack*>(t.get())) {
1064 // As with the left, it should not have vanished from the list
1065 assert(right->GetOwner().get() == &tracks);
1066 left->SetPan(-1.0f);
1067 right->SetPan(1.0f);
1070
1071 if(left->GetRate() != right->GetRate())
1072 //i18n-hint: explains why opened project was auto-modified
1073 onUnlink(XO("This project contained stereo tracks with different sample rates per channel."));
1074 if(left->GetSampleFormat() != right->GetSampleFormat())
1075 //i18n-hint: explains why opened project was auto-modified
1076 onUnlink(XO("This project contained stereo tracks with different sample formats in channels."));
1077 //i18n-hint: explains why opened project was auto-modified
1078 onUnlink(XO("This project contained stereo tracks with non-aligned content."));
1079 }
1080 }
1081 unlinkedTrack = nullptr;
1082 }
1083
1084 if (const auto message = t->GetErrorOpening()) {
1085 wxLogWarning(
1086 wxT("Track %s had error reading clip values from project file."),
1087 t->GetName());
1088 onError(*message);
1089 }
1090 }
1091}
1092
1094 const FilePath &fileName, bool addtohistory)
1095{
1096 // Allow extensions to update the project before opening it.
1099 return nullptr;
1100
1101 auto &project = mProject;
1102 auto &history = ProjectHistory::Get( project );
1103 auto &tracks = TrackList::Get( project );
1104 auto &trackPanel = TrackPanel::Get( project );
1105 auto &projectFileIO = ProjectFileIO::Get( project );
1106 auto &viewport = Viewport::Get( project );
1107
1108 auto results = ReadProjectFile( fileName );
1109 const bool bParseSuccess = results.parseSuccess;
1110 const auto &errorStr = results.errorString;
1111 const bool err = results.trackError;
1112
1113 if (bParseSuccess && !err) {
1115
1117 TrackFocus::Get(project).Set(*tracks.begin());
1118 viewport.HandleResize();
1119 trackPanel.Refresh(false);
1120
1121 // ? Old rationale in this comment no longer applies in 3.0.0, with no
1122 // more on-demand loading:
1123 trackPanel.Update(); // force any repaint to happen now,
1124 // else any asynch calls into the blockfile code will not have
1125 // finished logging errors (if any) before the call to ProjectFSCK()
1126
1127 if (addtohistory)
1128 FileHistory::Global().Append(fileName);
1129 }
1130
1131 if (bParseSuccess && !err) {
1132 if (projectFileIO.IsRecovered())
1133 {
1134 // PushState calls AutoSave(), so no longer need to do so here.
1135 history.PushState(XO("Project was recovered"), XO("Recover"));
1136 }
1137 return &project;
1138 }
1139 else {
1140 // Vaughan, 2011-10-30:
1141 // See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
1142 // Calling mTracks->Clear() with deleteTracks true results in data loss.
1143
1144 // PRL 2014-12-19:
1145 // I made many changes for wave track memory management, but only now
1146 // read the above comment. I may have invalidated the fix above (which
1147 // may have spared the files at the expense of leaked memory). But
1148 // here is a better way to accomplish the intent, doing like what happens
1149 // when the project closes:
1150 for (auto pTrack : tracks.Any<WaveTrack>())
1152
1153 tracks.Clear(); //tracks.Clear(true);
1154
1155 wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr.Debug());
1156
1157 projectFileIO.ShowError( *ProjectFramePlacement(&project),
1158 XO("Error Opening Project"),
1159 errorStr,
1160 results.helpUrl);
1161
1162 return nullptr;
1163 }
1164}
1165
1166void
1168 TrackHolders &&newTracks)
1169{
1170 auto &project = mProject;
1171 auto &history = ProjectHistory::Get( project );
1172 auto &projectFileIO = ProjectFileIO::Get( project );
1173 auto &tracks = TrackList::Get( project );
1174
1175 std::vector<Track*> results;
1176
1178
1179 wxFileName fn(fileName);
1180
1181 bool initiallyEmpty = tracks.empty();
1182 double newRate = 0;
1183 wxString trackNameBase = fn.GetName();
1184 int i = -1;
1185
1186 // Fix the bug 2109.
1187 // In case the project had soloed tracks before importing,
1188 // all newly imported tracks are muted.
1189 const bool projectHasSolo =
1190 !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
1191 if (projectHasSolo) {
1192 for (auto &group : newTracks)
1193 if (auto pTrack = dynamic_cast<PlayableTrack*>(group.get()))
1194 pTrack->SetMute(true);
1195 }
1196
1197 for (auto &group : newTracks) {
1198 if (auto pTrack = dynamic_cast<WaveTrack*>(group.get()))
1199 results.push_back(pTrack);
1200 tracks.Add(group);
1201 }
1202 newTracks.clear();
1203
1204 // Now name them
1205
1206 // Add numbers to track names only if there is more than one (mono or stereo)
1207 // track (not necessarily, more than one channel)
1208 const bool useSuffix = results.size() > 1;
1209
1210 for (const auto &newTrack : results) {
1211 ++i;
1212 newTrack->SetSelected(true);
1213 if (useSuffix)
1214 //i18n-hint Name default name assigned to a clip on track import
1215 newTrack->SetName(XC("%s %d", "clip name template")
1216 .Format(trackNameBase, i + 1).Translation());
1217 else
1218 newTrack->SetName(trackNameBase);
1219
1220 newTrack->TypeSwitch([&](WaveTrack &wt) {
1221 if (newRate == 0)
1222 newRate = wt.GetRate();
1223 const auto trackName = wt.GetName();
1224 for (const auto &interval : wt.Intervals())
1225 interval->SetName(trackName);
1226 });
1227 }
1228
1229 history.PushState(XO("Imported '%s'").Format( fileName ),
1230 XO("Import"));
1231
1232#if defined(__WXGTK__)
1233 // See bug #1224
1234 // The track panel hasn't been fully created, so ZoomFitHorizontally() will not give
1235 // expected results due to a window width of zero. Should be safe to yield here to
1236 // allow the creation to complete. If this becomes a problem, it "might" be possible
1237 // to queue a dummy event to trigger ZoomFitHorizontally().
1238 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
1239#endif
1240
1241 // If the project was clean and temporary (not permanently saved), then set
1242 // the filename to the just imported path.
1243 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1244 project.SetProjectName(fn.GetName());
1245 project.SetInitialImportPath(fn.GetPath());
1246 projectFileIO.SetProjectTitle();
1247 }
1248
1249 // Moved this call to higher levels to prevent flicker redrawing everything on each file.
1250 // HandleResize();
1251}
1252
1253namespace {
1254bool ImportProject(AudacityProject &dest, const FilePath &fileName)
1255{
1257 auto &project = temp.Project();
1258
1259 auto &projectFileIO = ProjectFileIO::Get(project);
1260 if (!projectFileIO.LoadProject(fileName, false))
1261 return false;
1262 auto &srcTracks = TrackList::Get(project);
1263 auto &destTracks = TrackList::Get(dest);
1264 for (const Track *pTrack : srcTracks)
1265 pTrack->PasteInto(dest, destTracks);
1267
1268 return true;
1269}
1270
1272 : public ImportProgressListener
1273{
1274 wxWeakRef<AudacityProject> mProject;
1275public:
1276
1278 : mProject(&project)
1279 {
1280
1281 }
1282
1283 bool OnImportFileOpened(ImportFileHandle& importFileHandle) override
1284 {
1285 mImportFileHandle = &importFileHandle;
1286 // File has more than one stream - display stream selector
1287 if (importFileHandle.GetStreamCount() > 1)
1288 {
1289 ImportStreamDialog ImportDlg(&importFileHandle, NULL, -1, XO("Select stream(s) to import"));
1290
1291 if (ImportDlg.ShowModal() == wxID_CANCEL)
1292 return false;
1293 }
1294 // One stream - import it by default
1295 else
1296 importFileHandle.SetStreamUsage(0,TRUE);
1297 return true;
1298 }
1299
1300 void OnImportProgress(double progress) override
1301 {
1302 constexpr double ProgressSteps { 1000.0 };
1303 if(!mProgressDialog)
1304 {
1305 wxFileName ff( mImportFileHandle->GetFilename() );
1306 auto title = XO("Importing %s").Format( mImportFileHandle->GetFileDescription() );
1307 mProgressDialog = BasicUI::MakeProgress(title, Verbatim(ff.GetFullName()));
1308 }
1309 auto result = mProgressDialog->Poll(progress * ProgressSteps, ProgressSteps);
1311 mImportFileHandle->Cancel();
1312 else if(result == BasicUI::ProgressResult::Stopped)
1313 mImportFileHandle->Stop();
1314 }
1315
1316 void OnImportResult(ImportResult result) override
1317 {
1318 mProgressDialog.reset();
1319 if(result == ImportResult::Error)
1320 {
1321 auto message = mImportFileHandle->GetErrorMessage();
1322 if(!message.empty())
1323 {
1324 AudacityMessageBox(message, XO("Import"), wxOK | wxCENTRE | wxICON_ERROR,
1325 mProject ? &GetProjectFrame(*mProject) : nullptr);
1326 }
1327 }
1328 }
1329
1330private:
1331
1332 ImportFileHandle* mImportFileHandle {nullptr};
1333 std::unique_ptr<BasicUI::ProgressDialog> mProgressDialog;
1334};
1335} // namespace
1336
1337bool ProjectFileManager::Import(const FilePath& fileName, bool addToHistory)
1338{
1339 wxArrayString fileNames;
1340 fileNames.Add(fileName);
1341 return Import(std::move(fileNames), addToHistory);
1342}
1343
1344bool ProjectFileManager::Import(wxArrayString fileNames, bool addToHistory)
1345{
1346 fileNames.Sort(FileNames::CompareNoCase);
1348 std::vector<wxString> { fileNames.begin(), fileNames.end() },
1349 addToHistory))
1350 return false;
1351 // Last track in the project is the one that was just added. Use it for
1352 // focus, selection, etc.
1353 Track* lastTrack = nullptr;
1354 const auto range = TrackList::Get(mProject).Any<Track>();
1355 assert(!range.empty());
1356 if(range.empty())
1357 return false;
1358 lastTrack = *(range.rbegin());
1359 BasicUI::CallAfter([wTrack = lastTrack->weak_from_this(), wProject = mProject.weak_from_this()] {
1360 const auto project = wProject.lock();
1361 const auto track = wTrack.lock();
1362 if (!project || !track)
1363 return;
1364 auto& viewPort = Viewport::Get(*project);
1365 TrackFocus::Get(*project).Set(track.get(), true);
1366 viewPort.ZoomFitHorizontally();
1367 viewPort.ShowTrack(*track);
1368 viewPort.HandleResize(); // Adjust scrollers for NEW track sizes.
1369 ViewInfo::Get(*project).selectedRegion.setTimes(
1370 track->GetStartTime(), track->GetEndTime());
1371 });
1372 return true;
1373}
1374
1375namespace
1376{
1377std::vector<std::shared_ptr<MIR::AnalyzedAudioClip>> RunTempoDetection(
1378 const std::vector<std::shared_ptr<ClipMirAudioReader>>& readers,
1379 const MIR::ProjectInterface& project, bool projectWasEmpty)
1380{
1381 const auto isBeatsAndMeasures = project.ViewIsBeatsAndMeasures();
1382 const auto projectTempo = project.GetTempo();
1383
1384 using namespace BasicUI;
1385 auto progress = MakeProgress(
1386 XO("Music Information Retrieval"), XO("Analyzing imported audio"),
1388 auto count = 0;
1389 const auto reportProgress = [&](double progressFraction) {
1390 const auto result = progress->Poll(
1391 (count + progressFraction) / readers.size() * 1000, 1000);
1392 if (result != ProgressResult::Success)
1393 throw UserException {};
1394 };
1395
1396 std::vector<std::shared_ptr<MIR::AnalyzedAudioClip>> analyzedClips;
1397 analyzedClips.reserve(readers.size());
1398 std::transform(
1399 readers.begin(), readers.end(), std::back_inserter(analyzedClips),
1400 [&](const std::shared_ptr<ClipMirAudioReader>& reader) {
1401 const MIR::ProjectSyncInfoInput input {
1402 *reader, reader->filename, reader->tags, reportProgress,
1403 projectTempo, projectWasEmpty, isBeatsAndMeasures,
1404 };
1405 auto syncInfo = MIR::GetProjectSyncInfo(input);
1406 ++count;
1407 return std::make_shared<AnalyzedWaveClip>(reader, syncInfo);
1408 });
1409 return analyzedClips;
1410}
1411} // namespace
1412
1414 const std::vector<FilePath>& fileNames, bool addToHistory)
1415{
1416 const auto projectWasEmpty =
1418 std::vector<std::shared_ptr<ClipMirAudioReader>> resultingReaders;
1419 const auto success = std::all_of(
1420 fileNames.begin(), fileNames.end(), [&](const FilePath& fileName) {
1421 std::shared_ptr<ClipMirAudioReader> resultingReader;
1422 const auto success = DoImport(fileName, addToHistory, resultingReader);
1423 if (success && resultingReader)
1424 resultingReaders.push_back(std::move(resultingReader));
1425 return success;
1426 });
1427 // At the moment, one failing import doesn't revert the project state, hence
1428 // we still run the analysis on what was successfully imported.
1429 // TODO implement reverting of the project state on failure.
1430 if (!resultingReaders.empty())
1431 {
1432 const auto pProj = mProject.shared_from_this();
1433 BasicUI::CallAfter([=] {
1434 AudacityMirProject mirInterface { *pProj };
1435 const auto analyzedClips =
1436 RunTempoDetection(resultingReaders, mirInterface, projectWasEmpty);
1437 MIR::SynchronizeProject(analyzedClips, mirInterface, projectWasEmpty);
1438 });
1439 }
1440 return success;
1441}
1442
1443// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
1445 const FilePath& fileName, bool addToHistory,
1446 std::shared_ptr<ClipMirAudioReader>& resultingReader)
1447{
1448 auto &project = mProject;
1449 auto &projectFileIO = ProjectFileIO::Get(project);
1450 auto oldTags = Tags::Get( project ).shared_from_this();
1451 bool initiallyEmpty = TrackList::Get(project).empty();
1452 TrackHolders newTracks;
1453 TranslatableString errorMessage;
1454
1455#ifdef EXPERIMENTAL_IMPORT_AUP3
1456 // Handle AUP3 ("project") files directly
1457 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1458 if (ImportProject(project, fileName)) {
1459 auto &history = ProjectHistory::Get(project);
1460
1461 // If the project was clean and temporary (not permanently saved), then set
1462 // the filename to the just imported path.
1463 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1464 wxFileName fn(fileName);
1465 project.SetProjectName(fn.GetName());
1466 project.SetInitialImportPath(fn.GetPath());
1467 projectFileIO.SetProjectTitle();
1468 }
1469
1470 history.PushState(XO("Imported '%s'").Format(fileName), XO("Import"));
1471
1472 if (addToHistory) {
1473 FileHistory::Global().Append(fileName);
1474 }
1475 }
1476 else {
1477 errorMessage = projectFileIO.GetLastError();
1478 if (errorMessage.empty()) {
1479 errorMessage = XO("Failed to import project");
1480 }
1481
1482 // Additional help via a Help button links to the manual.
1484 XO("Error Importing"),
1485 errorMessage, wxT("Importing_Audio"));
1486 }
1487
1488 return false;
1489 }
1490#endif
1491
1492 {
1493 // Backup Tags, before the import. Be prepared to roll back changes.
1494 bool committed = false;
1495 auto cleanup = finally([&]{
1496 if ( !committed )
1497 Tags::Set( project, oldTags );
1498 });
1499 auto newTags = oldTags->Duplicate();
1500 Tags::Set( project, newTags );
1501
1502#ifndef EXPERIMENTAL_IMPORT_AUP3
1503 // Handle AUP3 ("project") files specially
1504 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1506 XO("Error Importing"),
1507 XO( "Cannot import AUP3 format. Use File > Open instead"),
1508 wxT("File_Menu"));
1509 return false;
1510 }
1511#endif
1512
1513 ImportProgress importProgress(project);
1514 std::optional<LibFileFormats::AcidizerTags> acidTags;
1515 bool success = Importer::Get().Import(
1516 project, fileName, &importProgress, &WaveTrackFactory::Get(project),
1517 newTracks, newTags.get(), acidTags, errorMessage);
1518 if (!errorMessage.empty()) {
1519 // Error message derived from Importer::Import
1520 // Additional help via a Help button links to the manual.
1522 XO("Error Importing"), errorMessage, wxT("Importing_Audio"));
1523 }
1524 if (!success)
1525 return false;
1526
1527 const auto projectTempo = ProjectTimeSignature::Get(project).GetTempo();
1528 for (auto track : newTracks)
1529 DoProjectTempoChange(*track, projectTempo);
1530
1531 if (newTracks.size() == 1)
1532 {
1533 const auto waveTrack = dynamic_cast<WaveTrack*>(newTracks[0].get());
1534 // Also check that the track has a clip, as protection against empty
1535 // file import.
1536 if (waveTrack && !waveTrack->GetClipInterfaces().empty())
1537 resultingReader.reset(new ClipMirAudioReader {
1538 std::move(acidTags), fileName.ToStdString(),
1539 *waveTrack });
1540 }
1541
1542 if (addToHistory) {
1543 FileHistory::Global().Append(fileName);
1544 }
1545
1546 // no more errors, commit
1547 committed = true;
1548 }
1549
1550 // for LOF ("list of files") files, do not import the file as if it
1551 // were an audio file itself
1552 if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
1553 // PRL: don't redundantly do the steps below, because we already
1554 // did it in case of LOF, because of some weird recursion back to this
1555 // same function. I think this should be untangled.
1556
1557 // So Undo history push is not bypassed, despite appearances.
1558 return false;
1559 }
1560
1561 // Handle AUP ("legacy project") files directly
1562 if (fileName.AfterLast('.').IsSameAs(wxT("aup"), false)) {
1563 // If the project was clean and temporary (not permanently saved), then set
1564 // the filename to the just imported path.
1565 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1566 wxFileName fn(fileName);
1567 project.SetProjectName(fn.GetName());
1568 project.SetInitialImportPath(fn.GetPath());
1569 projectFileIO.SetProjectTitle();
1570 }
1571
1572 auto &history = ProjectHistory::Get( project );
1573
1574 history.PushState(XO("Imported '%s'").Format( fileName ), XO("Import"));
1575
1576 return true;
1577 }
1578
1579 // PRL: Undo history is incremented inside this:
1580 AddImportedTracks(fileName, std::move(newTracks));
1581
1582 return true;
1583}
1584
1585#include "Clipboard.h"
1586#include "ShuttleGui.h"
1587#include "HelpSystem.h"
1588
1589// Compact dialog
1590namespace {
1592{
1593public:
1595 : wxDialogWrapper(nullptr, wxID_ANY, XO("Compact Project"))
1596 {
1597 ShuttleGui S(this, eIsCreating);
1598
1599 S.StartVerticalLay(true);
1600 {
1601 S.AddFixedText(text, false, 500);
1602
1603 S.AddStandardButtons(eYesButton | eNoButton | eHelpButton);
1604 }
1605 S.EndVerticalLay();
1606
1607 FindWindowById(wxID_YES, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnYes, this);
1608 FindWindowById(wxID_NO, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnNo, this);
1609 FindWindowById(wxID_HELP, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnGetURL, this);
1610
1611 Layout();
1612 Fit();
1613 Center();
1614 }
1615
1616 void OnYes(wxCommandEvent &WXUNUSED(evt))
1617 {
1618 EndModal(wxYES);
1619 }
1620
1621 void OnNo(wxCommandEvent &WXUNUSED(evt))
1622 {
1623 EndModal(wxNO);
1624 }
1625
1626 void OnGetURL(wxCommandEvent &WXUNUSED(evt))
1627 {
1628 HelpSystem::ShowHelp(this, L"File_Menu:_Compact_Project", true);
1629 }
1630};
1631}
1632
1634{
1635 auto &project = mProject;
1636 auto &undoManager = UndoManager::Get(project);
1637 auto &clipboard = Clipboard::Get();
1638 auto &projectFileIO = ProjectFileIO::Get(project);
1639 bool isBatch = project.mBatchMode > 0;
1640
1641 // Purpose of this is to remove the -wal file.
1642 projectFileIO.ReopenProject();
1643
1644 auto savedState = undoManager.GetSavedState();
1645 const auto currentState = undoManager.GetCurrentState();
1646 if (savedState < 0) {
1647 undoManager.StateSaved();
1648 savedState = undoManager.GetSavedState();
1649 if (savedState < 0) {
1650 wxASSERT(false);
1651 savedState = 0;
1652 }
1653 }
1654 const auto least = std::min<size_t>(savedState, currentState);
1655 const auto greatest = std::max<size_t>(savedState, currentState);
1656 std::vector<const TrackList*> trackLists;
1657 auto fn = [&](const UndoStackElem& elem) {
1658 if (auto pTracks = UndoTracks::Find(elem))
1659 trackLists.push_back(pTracks);
1660 };
1661 undoManager.VisitStates(fn, least, 1 + least);
1662 if (least != greatest)
1663 undoManager.VisitStates(fn, greatest, 1 + greatest);
1664
1665 int64_t total = projectFileIO.GetTotalUsage();
1666 int64_t used = projectFileIO.GetCurrentUsage(trackLists);
1667
1668 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1669
1670 CompactDialog dlg(
1671 XO("Compacting this project will free up disk space by removing unused bytes within the file.\n\n"
1672 "There is %s of free disk space and this project is currently using %s.\n"
1673 "\n"
1674 "If you proceed, the current Undo/Redo History and clipboard contents will be discarded "
1675 "and you will recover approximately %s of disk space.\n"
1676 "\n"
1677 "Do you want to continue?")
1678 .Format(Internat::FormatSize(projectFileIO.GetFreeDiskSpace()),
1679 Internat::FormatSize(before.GetValue()),
1680 Internat::FormatSize(total - used)));
1681 if (isBatch || dlg.ShowModal() == wxYES)
1682 {
1683 // We can remove redo states, if they are after the saved state.
1684 undoManager.RemoveStates(1 + greatest, undoManager.GetNumStates());
1685
1686 // We can remove all states between the current and the last saved.
1687 if (least < greatest)
1688 undoManager.RemoveStates(least + 1, greatest);
1689
1690 // We can remove all states before the current and the last saved.
1691 undoManager.RemoveStates(0, least);
1692
1693 // And clear the clipboard, if needed
1694 if (&mProject == clipboard.Project().lock().get())
1695 clipboard.Clear();
1696
1697 // Refresh the before space usage since it may have changed due to the
1698 // above actions.
1699 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1700
1701 projectFileIO.Compact(trackLists, true);
1702
1703 auto after = wxFileName::GetSize(projectFileIO.GetFileName());
1704
1705 if (!isBatch)
1706 {
1708 XO("Compacting actually freed %s of disk space.")
1709 .Format(Internat::FormatSize((before - after).GetValue())),
1710 XO("Compact Project"));
1711 }
1712
1713 undoManager.RenameState( undoManager.GetCurrentState(),
1714 XO("Compacted project file"),
1715 XO("Compact") );
1716 }
1717}
1718
1719static void RefreshAllTitles(bool bShowProjectNumbers )
1720{
1721 for ( auto pProject : AllProjects{} ) {
1722 if ( !GetProjectFrame( *pProject ).IsIconized() ) {
1724 bShowProjectNumbers ? pProject->GetProjectNumber() : -1 );
1725 }
1726 }
1727}
1728
1730 wxTopLevelWindow &window, AudacityProject &project )
1731{
1732 if( window.IsIconized() )
1733 window.Restore();
1734 window.Raise(); // May help identifying the window on Mac
1735
1736 // Construct this project's name and number.
1737 sProjName = project.GetProjectName();
1738 if ( sProjName.empty() ) {
1739 sProjName = _("<untitled>");
1740 UnnamedCount = std::count_if(
1742 []( const AllProjects::value_type &ptr ){
1743 return ptr->GetProjectName().empty();
1744 }
1745 );
1746 if ( UnnamedCount > 1 ) {
1747 sProjNumber.Printf(
1748 _("[Project %02i] "), project.GetProjectNumber() + 1 );
1749 RefreshAllTitles( true );
1750 }
1751 }
1752 else
1753 UnnamedCount = 0;
1754}
1755
1757 if( UnnamedCount > 1 )
1758 RefreshAllTitles( false );
1759}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
XO("Cut/Copy/Paste")
The interface that all file import "plugins" (if you want to call them that) must implement....
std::vector< std::shared_ptr< Track > > TrackHolders
Definition: ImportRaw.h:24
#define XC(s, c)
Definition: Internat.h:37
#define _(s)
Definition: Internat.h:73
static const auto title
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
Definition: Project.cpp:129
wxString FilePath
Definition: Project.h:21
@ Cancel
Open was cancelled by the extension.
@ Handled
Save was handled by the extension.
@ Continue
Save was not handled by the extension.
@ Veto
Extension vetoed the close.
static const AudacityProject::AttachedObjects::RegisteredFactory sFileManagerKey
static void RefreshAllTitles(bool bShowProjectNumbers)
an object holding per-project preferred sample rate
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
accessors for certain important windows associated with each project
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
@ eIsCreating
Definition: ShuttleGui.h:37
@ eYesButton
Definition: ShuttleGui.h:611
@ eHelpButton
Definition: ShuttleGui.h:613
@ eNoButton
Definition: ShuttleGui.h:612
void DoProjectTempoChange(ChannelGroup &group, double newTempo)
Definition: TempoChange.cpp:41
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:51
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
An AudacityException with no visible message.
static const auto fn
const_iterator end() const
Definition: Project.cpp:27
Container::value_type value_type
Definition: Project.h:57
const_iterator begin() const
Definition: Project.cpp:22
Wrap wxMessageDialog so that caption IS translatable.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
static Clipboard & Get()
Definition: Clipboard.cpp:28
virtual int GetFilterIndex() const
virtual wxString GetPath() const
virtual void GetPaths(wxArrayString &paths) const
virtual void SetFilterIndex(int filterIndex)
virtual int ShowModal()
static TranslatableString WriteFailureMessage(const wxFileName &fileName)
void Append(const FilePath &file)
Definition: FileHistory.h:46
static FileHistory & Global()
Definition: FileHistory.cpp:39
FILES_API const FileType AudacityProjects
Definition: FileNames.h:71
Abstract base class used in importing a file.
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:231
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
Base class for FlacImportFileHandle, LOFImportFileHandle, MP3ImportFileHandle, OggImportFileHandle an...
Definition: ImportPlugin.h:111
virtual wxInt32 GetStreamCount()=0
virtual void SetStreamUsage(wxInt32 StreamID, bool Use)=0
Interface used to report on import state and progress.
static Importer & Get()
Definition: Import.cpp:103
static void SetLastOpenType(const FileNames::FileType &type)
Definition: Import.cpp:254
FileNames::FileTypes GetFileTypes(const FileNames::FileType &extraType={})
Definition: Import.cpp:209
bool Import(AudacityProject &project, const FilePath &fName, ImportProgressListener *importProgressListener, WaveTrackFactory *trackFactory, TrackHolders &tracks, Tags *tags, std::optional< LibFileFormats::AcidizerTags > &outAcidTags, TranslatableString &errorMessage)
Definition: Import.cpp:487
static size_t SelectDefaultOpenType(const FileNames::FileTypes &fileTypes)
Definition: Import.cpp:274
static void SetDefaultOpenType(const FileNames::FileType &type)
Definition: Import.cpp:264
static TranslatableString FormatSize(wxLongLong size)
Convert a number to a string while formatting it in bytes, KB, MB, GB.
Definition: Internat.cpp:179
Makes a temporary project that doesn't display on the screen.
AudacityProject & Project()
static void RebuildAllMenuBars()
static FilePath GetLongFileName(const FilePath &shortFileName)
AudioTrack subclass that can also be audibly replayed by the program.
Definition: PlayableTrack.h:40
bool GetSolo() const
Definition: PlayableTrack.h:48
static PluginManager & Get()
void SetProjectTitle(int number=-1)
static ProjectFileIO & Get(AudacityProject &project)
const FilePath & GetFileName() const
static void FixTracks(TrackList &tracks, const std::function< void(const TranslatableString &)> &onError, const std::function< void(const TranslatableString &)> &onUnlink)
Attempts to find and fix problems in tracks.
std::shared_ptr< TrackList > mLastSavedTracks
static bool IsAlreadyOpen(const FilePath &projPathName)
AudacityProject & mProject
bool Import(const FilePath &fileName, bool addToHistory=true)
bool ImportAndRunTempoDetection(const std::vector< FilePath > &fileNames, bool addToHistory)
bool SaveCopy(const FilePath &fileName=wxT(""))
bool SaveAs(bool allowOverwrite=false)
static void DiscardAutosave(const FilePath &filename)
void AddImportedTracks(const FilePath &fileName, TrackHolders &&newTracks)
static wxArrayString ShowOpenDialog(FileNames::Operation op, const FileNames::FileType &extraType={})
Show an open dialogue for opening audio files, and possibly other sorts of files.
bool DoSave(const FilePath &fileName, bool fromSaveAs)
bool SaveFromTimerRecording(wxFileName fnFile)
static AudacityProject * OpenFile(const ProjectChooserFn &chooser, const FilePath &fileName, bool addtohistory=true)
ReadProjectResults ReadProjectFile(const FilePath &fileName, bool discardAutosave=false)
bool DoImport(const FilePath &fileName, bool addToHistory, std::shared_ptr< ClipMirAudioReader > &resultingReader)
std::function< AudacityProject &(bool)> ProjectChooserFn
A function that returns a project to use for opening a file; argument is true if opening a project fi...
static ProjectFileManager & Get(AudacityProject &project)
ProjectFileManager(AudacityProject &project)
AudacityProject * OpenProjectFile(const FilePath &fileName, bool addtohistory)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=MainStatusBarField())
static ProjectTimeSignature & Get(AudacityProject &project)
static RealtimeEffectList & Get(AudacityProject &project)
void Clear()
Use only in the main thread. Sends Remove messages.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
void Merge(const Tags &other)
Definition: Tags.cpp:246
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static Tags & Set(AudacityProject &project, const std::shared_ptr< Tags > &tags)
Definition: Tags.cpp:224
TitleRestorer(wxTopLevelWindow &window, AudacityProject &project)
Track * Get()
Definition: TrackFocus.cpp:156
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
std::shared_ptr< Track > Holder
Definition: Track.h:202
const wxString & GetName() const
Name is always the same for all channels of a group.
Definition: Track.cpp:64
virtual Holder Duplicate(DuplicateOptions={}) const
public nonvirtual duplication function that invokes Clone()
Definition: Track.cpp:109
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
bool empty() const
Definition: Track.cpp:758
static TrackListHolder Create(AudacityProject *pOwner)
Definition: Track.cpp:330
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1016
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:234
Holds a msgid for the translation catalog; may also bind format arguments.
Identifier MSGID() const
MSGID is the English lookup key in the catalog, not necessarily for user's eyes if locale is some oth...
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
void StateSaved()
bool UnsavedChanges() const
An error dialog about unwritable location, that allows to navigate to settings quickly.
Can be thrown when user cancels operations, as with a progress dialog. Delayed handler does nothing.
Definition: UserException.h:17
void ReinitScrollbars()
Definition: Viewport.h:172
void ZoomFitHorizontallyAndShowTrack(Track *pTrack)
Definition: Viewport.cpp:443
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:33
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:3349
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void ZipClips(bool mustAlign=true)
Definition: WaveTrack.cpp:3290
double GetRate() const override
Definition: WaveTrack.cpp:798
auto Intervals()
Definition: WaveTrack.h:670
void OnImportResult(ImportResult result) override
Used to report on import result for file handle passed as argument to OnImportFileOpened.
bool OnImportFileOpened(ImportFileHandle &importFileHandle) override
std::unique_ptr< BasicUI::ProgressDialog > mProgressDialog
@ ProgressShowCancel
Definition: BasicUI.h:142
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:272
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
Definition: BasicUI.h:302
FILES_API bool IsOnFATFileSystem(const FilePath &path)
FILES_API bool IsPathAvailable(const FilePath &Path)
FILES_API wxString AbbreviatePath(const wxFileName &fileName)
Give enough of the path to identify the device. (On Windows, drive letter plus ':')
FILES_API void UpdateDefaultPath(Operation op, const FilePath &path)
FILES_API int CompareNoCase(const wxString &first, const wxString &second)
FILES_API FilePath FindDefaultPath(Operation op)
std::optional< ProjectSyncInfo > GetProjectSyncInfo(const ProjectSyncInfoInput &in)
void SynchronizeProject(const std::vector< std::shared_ptr< AnalyzedAudioClip > > &clips, ProjectInterface &project, bool projectWasEmpty)
void SelectNone(AudacityProject &project)
FILES_API bool FATFilesystemDenied(const FilePath &path, const TranslatableString &msg, const BasicUI::WindowPlacement &placement={})
FILES_API wxString UnsavedProjectFileName()
TRACK_API TrackList * Find(const UndoStackElem &state)
Definition: UndoTracks.cpp:47
WAVE_TRACK_API void CloseLock(WaveTrack &track) noexcept
Should be called upon project close. Not balanced by unlocking calls.
bool ImportProject(AudacityProject &dest, const FilePath &fileName)
std::pair< const char *, const char * > Pair
wxString FindHelpUrl(const TranslatableString &libraryError)
std::vector< std::shared_ptr< MIR::AnalyzedAudioClip > > RunTempoDetection(const std::vector< std::shared_ptr< ClipMirAudioReader > > &readers, const MIR::ProjectInterface &project, bool projectWasEmpty)
TranslatableString Message(unsigned trackCount)
std::string ToUTF8(const std::wstring &wstr)
wxString ToWXString(const std::string &str)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:52
static void OnLoad(AudacityProject &project)
static OnOpenAction OnOpen(AudacityProject &project, const std::string &path)
static OnSaveAction OnSave(AudacityProject &project, const ProjectSaveCallback &projectSaveCallback)
static OnCloseAction OnClose(AudacityProject &project)
Choices when duplicating a track.
Definition: Track.h:271
DuplicateOptions Backup() &&
Definition: Track.h:288
Holds one item with description and time range for the UndoManager.
Definition: UndoManager.h:117