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