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}
804
806{
807 auto &project = mProject;
808 auto &projectFileIO = ProjectFileIO::Get(project);
809
810 return projectFileIO.OpenProject();
811}
812
814{
815 auto &project = mProject;
816 auto &projectFileIO = ProjectFileIO::Get(project);
817
818 bool bOK = OpenProject();
819 if( !bOK )
820 {
821 auto tmpdir = wxFileName(TempDirectory::UnsavedProjectFileName()).GetPath();
822
823 UnwritableLocationErrorDialog dlg(nullptr, tmpdir);
824 dlg.ShowModal();
825 }
826 return bOK;
827}
828
830{
831 auto &project = mProject;
832 auto &projectFileIO = ProjectFileIO::Get(project);
833
834 projectFileIO.CloseProject();
835
836 // Blocks were locked in CompactProjectOnClose, so DELETE the data structure so that
837 // there's no memory leak.
839 {
840 mLastSavedTracks->Clear();
841 mLastSavedTracks.reset();
842 }
843}
844
845// static method, can be called outside of a project
847 const FileNames::FileType &extraType )
848{
849 // Construct the filter
850 const auto fileTypes = Importer::Get().GetFileTypes( extraType );
851
852 // Retrieve saved path
853 auto path = FileNames::FindDefaultPath(op);
854
855 // Construct and display the file dialog
856 wxArrayString selected;
857
858 FileDialogWrapper dlog(nullptr,
859 XO("Select one or more files"),
860 path,
861 wxT(""),
862 fileTypes,
863 wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
864
866
867 int dialogResult = dlog.ShowModal();
868
869 // Convert the filter index to type and save
870 auto index = dlog.GetFilterIndex();
871 const auto &saveType = fileTypes[ index ];
872
874 Importer::SetLastOpenType( saveType );
875
876 if (dialogResult == wxID_OK) {
877 // Return the selected files
878 dlog.GetPaths(selected);
879
880 // Remember the directory
881 FileNames::UpdateDefaultPath(op, ::wxPathOnly(dlog.GetPath()));
882 }
883
884 return selected;
885}
886
887// static method, can be called outside of a project
889{
890 const wxFileName newProjPathName(projPathName);
891 auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
892 iter = std::find_if( start, finish,
893 [&]( const AllProjects::value_type &ptr ){
894 return newProjPathName.SameAs(wxFileNameWrapper{ ProjectFileIO::Get(*ptr).GetFileName() });
895 } );
896 if (iter != finish) {
897 auto errMsg =
898 XO("%s is already open in another window.")
899 .Format( newProjPathName.GetName() );
900 wxLogError(errMsg.Translation()); //Debug?
902 errMsg,
903 XO("Error Opening Project"),
904 wxOK | wxCENTRE);
905 return true;
906 }
907 return false;
908}
909
911 const FilePath &fileNameArg, bool addtohistory)
912{
913 // On Win32, we may be given a short (DOS-compatible) file name on rare
914 // occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
915 // convert these to long file name first.
916 auto fileName = PlatformCompatibility::GetLongFileName(fileNameArg);
917
918 // Make sure it isn't already open.
919 // Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
920 // and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
921 // from, e.g., Win Explorer, it would bypass those, get to here with no check,
922 // then open a NEW project from the same data with no warning.
923 // This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
924 // but is not really part of that bug. Anyway, prevent it!
925 if (IsAlreadyOpen(fileName))
926 return nullptr;
927
928 // Data loss may occur if users mistakenly try to open ".aup3.bak" files
929 // left over from an unsuccessful save or by previous versions of Audacity.
930 // So we always refuse to open such files.
931 if (fileName.Lower().EndsWith(wxT(".aup3.bak")))
932 {
934 XO(
935"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."),
936 XO("Warning - Backup File Detected"),
937 wxOK | wxCENTRE,
938 nullptr);
939 return nullptr;
940 }
941
942 if (!::wxFileExists(fileName)) {
944 XO("Could not open file: %s").Format( fileName ),
945 XO("Error Opening File"),
946 wxOK | wxCENTRE,
947 nullptr);
948 return nullptr;
949 }
950
951 // Following block covers cases other than a project file:
952 {
953 wxFFile ff(fileName, wxT("rb"));
954
955 auto cleanup = finally([&]
956 {
957 if (ff.IsOpened())
958 {
959 ff.Close();
960 }
961 });
962
963 if (!ff.IsOpened()) {
965 XO("Could not open file: %s").Format( fileName ),
966 XO("Error opening file"),
967 wxOK | wxCENTRE,
968 nullptr);
969 return nullptr;
970 }
971
972 char buf[7];
973 auto numRead = ff.Read(buf, 6);
974 if (numRead != 6) {
976 XO("File may be invalid or corrupted: \n%s").Format( fileName ),
977 XO("Error Opening File or Project"),
978 wxOK | wxCENTRE,
979 nullptr);
980 return nullptr;
981 }
982
983 if (wxStrncmp(buf, "SQLite", 6) != 0)
984 {
985 // Not a database
986#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
987 // Is it a plug-in?
988 if (PluginManager::Get().DropFile(fileName)) {
990 // Plug-in installation happened, not really opening of a file,
991 // so return null
992 return nullptr;
993 }
994#endif
995 auto &project = chooser(false);
996 // Undo history is incremented inside this:
997 if (Get(project).Import(fileName))
998 {
999 // Undo history is incremented inside this:
1000 // Bug 2743: Don't zoom with lof.
1001 if (!fileName.AfterLast('.').IsSameAs(wxT("lof"), false))
1003 return &project;
1004 }
1005 return nullptr;
1006 }
1007 }
1008
1009 // Disallow opening of .aup3 project files from FAT drives, but only such
1010 // files, not importable types. (Bug 2800)
1012 XO("Project resides on FAT formatted drive.\n"
1013 "Copy it to another drive to open it.")))
1014 {
1015 return nullptr;
1016 }
1017
1018 auto &project = chooser(true);
1019 return Get(project).OpenProjectFile(fileName, addtohistory);
1020}
1021
1023 const std::function<void(const TranslatableString&)>& onError,
1024 const std::function<void(const TranslatableString&)>& onUnlink)
1025{
1026 // This is successively assigned the left member of each pair that
1027 // becomes unlinked
1028 Track::Holder unlinkedTrack;
1029 // Beware iterator invalidation, because stereo channels get zipped,
1030 // replacing WaveTracks
1031 for (auto iter = tracks.begin(); iter != tracks.end();) {
1032 auto t = (*iter++)->SharedPointer();
1033 const auto linkType = t->GetLinkType();
1034 // Note, the next function may have an important upgrading side effect,
1035 // and return no error; or it may find a real error and repair it, but
1036 // that repaired track won't be used because opening will fail.
1037 if (!t->LinkConsistencyFix()) {
1038 onError(XO("A channel of a stereo track was missing."));
1039 unlinkedTrack = nullptr;
1040 }
1041 if (!unlinkedTrack) {
1042 if (linkType != ChannelGroup::LinkType::None &&
1043 t->NChannels() == 1) {
1044 // The track became unlinked.
1045 // It should NOT have been replaced with a "zip"
1046 assert(t->GetOwner().get() == &tracks);
1047 // Wait until LinkConsistencyFix is called on the second track
1048 unlinkedTrack = t;
1049 // Fix the iterator, which skipped the right channel before the
1050 // unlinking
1051 iter = tracks.Find(t.get());
1052 ++iter;
1053 }
1054 }
1055 else {
1056 //Not an elegant way to deal with stereo wave track linking
1057 //compatibility between versions
1058 if (const auto left = dynamic_cast<WaveTrack*>(unlinkedTrack.get())) {
1059 if (const auto right = dynamic_cast<WaveTrack*>(t.get())) {
1060 // As with the left, it should not have vanished from the list
1061 assert(right->GetOwner().get() == &tracks);
1062 left->SetPan(-1.0f);
1063 right->SetPan(1.0f);
1066
1067 if(left->GetRate() != right->GetRate())
1068 //i18n-hint: explains why opened project was auto-modified
1069 onUnlink(XO("This project contained stereo tracks with different sample rates per channel."));
1070 if(left->GetSampleFormat() != right->GetSampleFormat())
1071 //i18n-hint: explains why opened project was auto-modified
1072 onUnlink(XO("This project contained stereo tracks with different sample formats in channels."));
1073 //i18n-hint: explains why opened project was auto-modified
1074 onUnlink(XO("This project contained stereo tracks with non-aligned content."));
1075 }
1076 }
1077 unlinkedTrack = nullptr;
1078 }
1079
1080 if (const auto message = t->GetErrorOpening()) {
1081 wxLogWarning(
1082 wxT("Track %s had error reading clip values from project file."),
1083 t->GetName());
1084 onError(*message);
1085 }
1086 }
1087}
1088
1090 const FilePath &fileName, bool addtohistory)
1091{
1092 // Allow extensions to update the project before opening it.
1095 return nullptr;
1096
1097 auto &project = mProject;
1098 auto &history = ProjectHistory::Get( project );
1099 auto &tracks = TrackList::Get( project );
1100 auto &trackPanel = TrackPanel::Get( project );
1101 auto &projectFileIO = ProjectFileIO::Get( project );
1102 auto &viewport = Viewport::Get( project );
1103
1104 auto results = ReadProjectFile( fileName );
1105 const bool bParseSuccess = results.parseSuccess;
1106 const auto &errorStr = results.errorString;
1107 const bool err = results.trackError;
1108
1109 if (bParseSuccess && !err) {
1111
1113 TrackFocus::Get(project).Set(*tracks.begin());
1114 viewport.HandleResize();
1115 trackPanel.Refresh(false);
1116
1117 // ? Old rationale in this comment no longer applies in 3.0.0, with no
1118 // more on-demand loading:
1119 trackPanel.Update(); // force any repaint to happen now,
1120 // else any asynch calls into the blockfile code will not have
1121 // finished logging errors (if any) before the call to ProjectFSCK()
1122
1123 if (addtohistory)
1124 FileHistory::Global().Append(fileName);
1125 }
1126
1127 if (bParseSuccess && !err) {
1128 if (projectFileIO.IsRecovered())
1129 {
1130 // PushState calls AutoSave(), so no longer need to do so here.
1131 history.PushState(XO("Project was recovered"), XO("Recover"));
1132 }
1133 return &project;
1134 }
1135 else {
1136 // Vaughan, 2011-10-30:
1137 // See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
1138 // Calling mTracks->Clear() with deleteTracks true results in data loss.
1139
1140 // PRL 2014-12-19:
1141 // I made many changes for wave track memory management, but only now
1142 // read the above comment. I may have invalidated the fix above (which
1143 // may have spared the files at the expense of leaked memory). But
1144 // here is a better way to accomplish the intent, doing like what happens
1145 // when the project closes:
1146 for (auto pTrack : tracks.Any<WaveTrack>())
1148
1149 tracks.Clear(); //tracks.Clear(true);
1150
1151 wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr.Debug());
1152
1153 projectFileIO.ShowError( *ProjectFramePlacement(&project),
1154 XO("Error Opening Project"),
1155 errorStr,
1156 results.helpUrl);
1157
1158 return nullptr;
1159 }
1160}
1161
1162void
1164 TrackHolders &&newTracks)
1165{
1166 auto &project = mProject;
1167 auto &history = ProjectHistory::Get( project );
1168 auto &projectFileIO = ProjectFileIO::Get( project );
1169 auto &tracks = TrackList::Get( project );
1170
1171 std::vector<Track*> results;
1172
1174
1175 wxFileName fn(fileName);
1176
1177 bool initiallyEmpty = tracks.empty();
1178 double newRate = 0;
1179 wxString trackNameBase = fn.GetName();
1180 int i = -1;
1181
1182 // Fix the bug 2109.
1183 // In case the project had soloed tracks before importing,
1184 // all newly imported tracks are muted.
1185 const bool projectHasSolo =
1186 !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
1187 if (projectHasSolo) {
1188 for (auto &group : newTracks)
1189 if (auto pTrack = dynamic_cast<PlayableTrack*>(group.get()))
1190 pTrack->SetMute(true);
1191 }
1192
1193 for (auto &group : newTracks) {
1194 if (auto pTrack = dynamic_cast<WaveTrack*>(group.get()))
1195 results.push_back(pTrack);
1196 tracks.Add(group);
1197 }
1198 newTracks.clear();
1199
1200 // Now name them
1201
1202 // Add numbers to track names only if there is more than one (mono or stereo)
1203 // track (not necessarily, more than one channel)
1204 const bool useSuffix = results.size() > 1;
1205
1206 for (const auto &newTrack : results) {
1207 ++i;
1208 newTrack->SetSelected(true);
1209 if (useSuffix)
1210 //i18n-hint Name default name assigned to a clip on track import
1211 newTrack->SetName(XC("%s %d", "clip name template")
1212 .Format(trackNameBase, i + 1).Translation());
1213 else
1214 newTrack->SetName(trackNameBase);
1215
1216 newTrack->TypeSwitch([&](WaveTrack &wt) {
1217 if (newRate == 0)
1218 newRate = wt.GetRate();
1219 const auto trackName = wt.GetName();
1220 for (const auto &interval : wt.Intervals())
1221 interval->SetName(trackName);
1222 });
1223 }
1224
1225 history.PushState(XO("Imported '%s'").Format( fileName ),
1226 XO("Import"));
1227
1228#if defined(__WXGTK__)
1229 // See bug #1224
1230 // The track panel hasn't been fully created, so ZoomFitHorizontally() will not give
1231 // expected results due to a window width of zero. Should be safe to yield here to
1232 // allow the creation to complete. If this becomes a problem, it "might" be possible
1233 // to queue a dummy event to trigger ZoomFitHorizontally().
1234 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
1235#endif
1236
1237 // If the project was clean and temporary (not permanently saved), then set
1238 // the filename to the just imported path.
1239 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1240 project.SetProjectName(fn.GetName());
1241 project.SetInitialImportPath(fn.GetPath());
1242 projectFileIO.SetProjectTitle();
1243 }
1244
1245 // Moved this call to higher levels to prevent flicker redrawing everything on each file.
1246 // HandleResize();
1247}
1248
1249namespace {
1250bool ImportProject(AudacityProject &dest, const FilePath &fileName)
1251{
1253 auto &project = temp.Project();
1254
1255 auto &projectFileIO = ProjectFileIO::Get(project);
1256 if (!projectFileIO.LoadProject(fileName, false))
1257 return false;
1258 auto &srcTracks = TrackList::Get(project);
1259 auto &destTracks = TrackList::Get(dest);
1260 for (const Track *pTrack : srcTracks)
1261 pTrack->PasteInto(dest, destTracks);
1263
1264 return true;
1265}
1266
1268 : public ImportProgressListener
1269{
1270 wxWeakRef<AudacityProject> mProject;
1271public:
1272
1274 : mProject(&project)
1275 {
1276
1277 }
1278
1279 bool OnImportFileOpened(ImportFileHandle& importFileHandle) override
1280 {
1281 mImportFileHandle = &importFileHandle;
1282 // File has more than one stream - display stream selector
1283 if (importFileHandle.GetStreamCount() > 1)
1284 {
1285 ImportStreamDialog ImportDlg(&importFileHandle, NULL, -1, XO("Select stream(s) to import"));
1286
1287 if (ImportDlg.ShowModal() == wxID_CANCEL)
1288 return false;
1289 }
1290 // One stream - import it by default
1291 else
1292 importFileHandle.SetStreamUsage(0,TRUE);
1293 return true;
1294 }
1295
1296 void OnImportProgress(double progress) override
1297 {
1298 constexpr double ProgressSteps { 1000.0 };
1299 if(!mProgressDialog)
1300 {
1301 wxFileName ff( mImportFileHandle->GetFilename() );
1302 auto title = XO("Importing %s").Format( mImportFileHandle->GetFileDescription() );
1303 mProgressDialog = BasicUI::MakeProgress(title, Verbatim(ff.GetFullName()));
1304 }
1305 auto result = mProgressDialog->Poll(progress * ProgressSteps, ProgressSteps);
1307 mImportFileHandle->Cancel();
1308 else if(result == BasicUI::ProgressResult::Stopped)
1309 mImportFileHandle->Stop();
1310 }
1311
1312 void OnImportResult(ImportResult result) override
1313 {
1314 mProgressDialog.reset();
1315 if(result == ImportResult::Error)
1316 {
1317 auto message = mImportFileHandle->GetErrorMessage();
1318 if(!message.empty())
1319 {
1320 AudacityMessageBox(message, XO("Import"), wxOK | wxCENTRE | wxICON_ERROR,
1321 mProject ? &GetProjectFrame(*mProject) : nullptr);
1322 }
1323 }
1324 }
1325
1326private:
1327
1328 ImportFileHandle* mImportFileHandle {nullptr};
1329 std::unique_ptr<BasicUI::ProgressDialog> mProgressDialog;
1330};
1331} // namespace
1332
1333bool ProjectFileManager::Import(const FilePath& fileName, bool addToHistory)
1334{
1335 wxArrayString fileNames;
1336 fileNames.Add(fileName);
1337 return Import(std::move(fileNames), addToHistory);
1338}
1339
1340bool ProjectFileManager::Import(wxArrayString fileNames, bool addToHistory)
1341{
1342 fileNames.Sort(FileNames::CompareNoCase);
1344 std::vector<wxString> { fileNames.begin(), fileNames.end() },
1345 addToHistory))
1346 return false;
1347 // Last track in the project is the one that was just added. Use it for
1348 // focus, etc.
1349 Track* lastTrack = nullptr;
1350 const auto range = TrackList::Get(mProject).Any<Track>();
1351 assert(!range.empty());
1352 if(range.empty())
1353 return false;
1354 lastTrack = *(range.rbegin());
1355 BasicUI::CallAfter([wTrack = lastTrack->weak_from_this(), wProject = mProject.weak_from_this()] {
1356 const auto project = wProject.lock();
1357 const auto track = wTrack.lock();
1358 if (!project || !track)
1359 return;
1360 auto& viewPort = Viewport::Get(*project);
1361 TrackFocus::Get(*project).Set(track.get(), true);
1362 viewPort.ZoomFitHorizontally();
1363 viewPort.ShowTrack(*track);
1364 viewPort.HandleResize(); // Adjust scrollers for NEW track sizes.
1365 });
1366 return true;
1367}
1368
1369namespace
1370{
1371std::vector<std::shared_ptr<MIR::AnalyzedAudioClip>> RunTempoDetection(
1372 const std::vector<std::shared_ptr<ClipMirAudioReader>>& readers,
1373 const MIR::ProjectInterface& project, bool projectWasEmpty)
1374{
1375 const auto isBeatsAndMeasures = project.ViewIsBeatsAndMeasures();
1376 const auto projectTempo = project.GetTempo();
1377
1378 using namespace BasicUI;
1379 auto progress = MakeProgress(
1380 XO("Music Information Retrieval"), XO("Analyzing imported audio"),
1382 auto count = 0;
1383 const auto reportProgress = [&](double progressFraction) {
1384 const auto result = progress->Poll(
1385 (count + progressFraction) / readers.size() * 1000, 1000);
1386 if (result != ProgressResult::Success)
1387 throw UserException {};
1388 };
1389
1390 std::vector<std::shared_ptr<MIR::AnalyzedAudioClip>> analyzedClips;
1391 analyzedClips.reserve(readers.size());
1392 std::transform(
1393 readers.begin(), readers.end(), std::back_inserter(analyzedClips),
1394 [&](const std::shared_ptr<ClipMirAudioReader>& reader) {
1395 const MIR::ProjectSyncInfoInput input {
1396 *reader, reader->filename, reader->tags, reportProgress,
1397 projectTempo, projectWasEmpty, isBeatsAndMeasures,
1398 };
1399 auto syncInfo = MIR::GetProjectSyncInfo(input);
1400 ++count;
1401 return std::make_shared<AnalyzedWaveClip>(reader, syncInfo);
1402 });
1403 return analyzedClips;
1404}
1405} // namespace
1406
1408 const std::vector<FilePath>& fileNames, bool addToHistory)
1409{
1410 const auto projectWasEmpty =
1412 std::vector<std::shared_ptr<ClipMirAudioReader>> resultingReaders;
1413 const auto success = std::all_of(
1414 fileNames.begin(), fileNames.end(), [&](const FilePath& fileName) {
1415 std::shared_ptr<ClipMirAudioReader> resultingReader;
1416 const auto success = DoImport(fileName, addToHistory, resultingReader);
1417 if (success && resultingReader)
1418 resultingReaders.push_back(std::move(resultingReader));
1419 return success;
1420 });
1421 // At the moment, one failing import doesn't revert the project state, hence
1422 // we still run the analysis on what was successfully imported.
1423 // TODO implement reverting of the project state on failure.
1424 if (!resultingReaders.empty())
1425 {
1426 const auto pProj = mProject.shared_from_this();
1427 BasicUI::CallAfter([=] {
1428 AudacityMirProject mirInterface { *pProj };
1429 const auto analyzedClips =
1430 RunTempoDetection(resultingReaders, mirInterface, projectWasEmpty);
1431 MIR::SynchronizeProject(analyzedClips, mirInterface, projectWasEmpty);
1432 });
1433 }
1434 return success;
1435}
1436
1437// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
1439 const FilePath& fileName, bool addToHistory,
1440 std::shared_ptr<ClipMirAudioReader>& resultingReader)
1441{
1442 auto &project = mProject;
1443 auto &projectFileIO = ProjectFileIO::Get(project);
1444 auto oldTags = Tags::Get( project ).shared_from_this();
1445 bool initiallyEmpty = TrackList::Get(project).empty();
1446 TrackHolders newTracks;
1447 TranslatableString errorMessage;
1448
1449#ifdef EXPERIMENTAL_IMPORT_AUP3
1450 // Handle AUP3 ("project") files directly
1451 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1452 if (ImportProject(project, fileName)) {
1453 auto &history = ProjectHistory::Get(project);
1454
1455 // If the project was clean and temporary (not permanently saved), then set
1456 // the filename to the just imported path.
1457 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1458 wxFileName fn(fileName);
1459 project.SetProjectName(fn.GetName());
1460 project.SetInitialImportPath(fn.GetPath());
1461 projectFileIO.SetProjectTitle();
1462 }
1463
1464 history.PushState(XO("Imported '%s'").Format(fileName), XO("Import"));
1465
1466 if (addToHistory) {
1467 FileHistory::Global().Append(fileName);
1468 }
1469 }
1470 else {
1471 errorMessage = projectFileIO.GetLastError();
1472 if (errorMessage.empty()) {
1473 errorMessage = XO("Failed to import project");
1474 }
1475
1476 // Additional help via a Help button links to the manual.
1478 XO("Error Importing"),
1479 errorMessage, wxT("Importing_Audio"));
1480 }
1481
1482 return false;
1483 }
1484#endif
1485
1486 {
1487 // Backup Tags, before the import. Be prepared to roll back changes.
1488 bool committed = false;
1489 auto cleanup = finally([&]{
1490 if ( !committed )
1491 Tags::Set( project, oldTags );
1492 });
1493 auto newTags = oldTags->Duplicate();
1494 Tags::Set( project, newTags );
1495
1496#ifndef EXPERIMENTAL_IMPORT_AUP3
1497 // Handle AUP3 ("project") files specially
1498 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1500 XO("Error Importing"),
1501 XO( "Cannot import AUP3 format. Use File > Open instead"),
1502 wxT("File_Menu"));
1503 return false;
1504 }
1505#endif
1506
1507 ImportProgress importProgress(project);
1508 std::optional<LibFileFormats::AcidizerTags> acidTags;
1509 bool success = Importer::Get().Import(
1510 project, fileName, &importProgress, &WaveTrackFactory::Get(project),
1511 newTracks, newTags.get(), acidTags, errorMessage);
1512 if (!errorMessage.empty()) {
1513 // Error message derived from Importer::Import
1514 // Additional help via a Help button links to the manual.
1516 XO("Error Importing"), errorMessage, wxT("Importing_Audio"));
1517 }
1518 if (!success)
1519 return false;
1520
1521 const auto projectTempo = ProjectTimeSignature::Get(project).GetTempo();
1522 for (auto track : newTracks)
1523 DoProjectTempoChange(*track, projectTempo);
1524
1525 if (newTracks.size() == 1)
1526 {
1527 const auto waveTrack = dynamic_cast<WaveTrack*>(newTracks[0].get());
1528 // Also check that the track has a clip, as protection against empty
1529 // file import.
1530 if (waveTrack && !waveTrack->GetClipInterfaces().empty())
1531 resultingReader.reset(new ClipMirAudioReader {
1532 std::move(acidTags), fileName.ToStdString(),
1533 *waveTrack });
1534 }
1535
1536 if (addToHistory) {
1537 FileHistory::Global().Append(fileName);
1538 }
1539
1540 // no more errors, commit
1541 committed = true;
1542 }
1543
1544 // for LOF ("list of files") files, do not import the file as if it
1545 // were an audio file itself
1546 if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
1547 // PRL: don't redundantly do the steps below, because we already
1548 // did it in case of LOF, because of some weird recursion back to this
1549 // same function. I think this should be untangled.
1550
1551 // So Undo history push is not bypassed, despite appearances.
1552 return false;
1553 }
1554
1555 // Handle AUP ("legacy project") files directly
1556 if (fileName.AfterLast('.').IsSameAs(wxT("aup"), false)) {
1557 // If the project was clean and temporary (not permanently saved), then set
1558 // the filename to the just imported path.
1559 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1560 wxFileName fn(fileName);
1561 project.SetProjectName(fn.GetName());
1562 project.SetInitialImportPath(fn.GetPath());
1563 projectFileIO.SetProjectTitle();
1564 }
1565
1566 auto &history = ProjectHistory::Get( project );
1567
1568 history.PushState(XO("Imported '%s'").Format( fileName ), XO("Import"));
1569
1570 return true;
1571 }
1572
1573 // PRL: Undo history is incremented inside this:
1574 AddImportedTracks(fileName, std::move(newTracks));
1575
1576 return true;
1577}
1578
1579#include "Clipboard.h"
1580#include "ShuttleGui.h"
1581#include "HelpSystem.h"
1582
1583// Compact dialog
1584namespace {
1586{
1587public:
1589 : wxDialogWrapper(nullptr, wxID_ANY, XO("Compact Project"))
1590 {
1591 ShuttleGui S(this, eIsCreating);
1592
1593 S.StartVerticalLay(true);
1594 {
1595 S.AddFixedText(text, false, 500);
1596
1597 S.AddStandardButtons(eYesButton | eNoButton | eHelpButton);
1598 }
1599 S.EndVerticalLay();
1600
1601 FindWindowById(wxID_YES, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnYes, this);
1602 FindWindowById(wxID_NO, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnNo, this);
1603 FindWindowById(wxID_HELP, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnGetURL, this);
1604
1605 Layout();
1606 Fit();
1607 Center();
1608 }
1609
1610 void OnYes(wxCommandEvent &WXUNUSED(evt))
1611 {
1612 EndModal(wxYES);
1613 }
1614
1615 void OnNo(wxCommandEvent &WXUNUSED(evt))
1616 {
1617 EndModal(wxNO);
1618 }
1619
1620 void OnGetURL(wxCommandEvent &WXUNUSED(evt))
1621 {
1622 HelpSystem::ShowHelp(this, L"File_Menu:_Compact_Project", true);
1623 }
1624};
1625}
1626
1628{
1629 auto &project = mProject;
1630 auto &undoManager = UndoManager::Get(project);
1631 auto &clipboard = Clipboard::Get();
1632 auto &projectFileIO = ProjectFileIO::Get(project);
1633 bool isBatch = project.mBatchMode > 0;
1634
1635 // Purpose of this is to remove the -wal file.
1636 projectFileIO.ReopenProject();
1637
1638 auto savedState = undoManager.GetSavedState();
1639 const auto currentState = undoManager.GetCurrentState();
1640 if (savedState < 0) {
1641 undoManager.StateSaved();
1642 savedState = undoManager.GetSavedState();
1643 if (savedState < 0) {
1644 wxASSERT(false);
1645 savedState = 0;
1646 }
1647 }
1648 const auto least = std::min<size_t>(savedState, currentState);
1649 const auto greatest = std::max<size_t>(savedState, currentState);
1650 std::vector<const TrackList*> trackLists;
1651 auto fn = [&](const UndoStackElem& elem) {
1652 if (auto pTracks = UndoTracks::Find(elem))
1653 trackLists.push_back(pTracks);
1654 };
1655 undoManager.VisitStates(fn, least, 1 + least);
1656 if (least != greatest)
1657 undoManager.VisitStates(fn, greatest, 1 + greatest);
1658
1659 int64_t total = projectFileIO.GetTotalUsage();
1660 int64_t used = projectFileIO.GetCurrentUsage(trackLists);
1661
1662 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1663
1664 CompactDialog dlg(
1665 XO("Compacting this project will free up disk space by removing unused bytes within the file.\n\n"
1666 "There is %s of free disk space and this project is currently using %s.\n"
1667 "\n"
1668 "If you proceed, the current Undo/Redo History and clipboard contents will be discarded "
1669 "and you will recover approximately %s of disk space.\n"
1670 "\n"
1671 "Do you want to continue?")
1672 .Format(Internat::FormatSize(projectFileIO.GetFreeDiskSpace()),
1673 Internat::FormatSize(before.GetValue()),
1674 Internat::FormatSize(total - used)));
1675 if (isBatch || dlg.ShowModal() == wxYES)
1676 {
1677 // We can remove redo states, if they are after the saved state.
1678 undoManager.RemoveStates(1 + greatest, undoManager.GetNumStates());
1679
1680 // We can remove all states between the current and the last saved.
1681 if (least < greatest)
1682 undoManager.RemoveStates(least + 1, greatest);
1683
1684 // We can remove all states before the current and the last saved.
1685 undoManager.RemoveStates(0, least);
1686
1687 // And clear the clipboard, if needed
1688 if (&mProject == clipboard.Project().lock().get())
1689 clipboard.Clear();
1690
1691 // Refresh the before space usage since it may have changed due to the
1692 // above actions.
1693 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1694
1695 projectFileIO.Compact(trackLists, true);
1696
1697 auto after = wxFileName::GetSize(projectFileIO.GetFileName());
1698
1699 if (!isBatch)
1700 {
1702 XO("Compacting actually freed %s of disk space.")
1703 .Format(Internat::FormatSize((before - after).GetValue())),
1704 XO("Compact Project"));
1705 }
1706
1707 undoManager.RenameState( undoManager.GetCurrentState(),
1708 XO("Compacted project file"),
1709 XO("Compact") );
1710 }
1711}
1712
1713static void RefreshAllTitles(bool bShowProjectNumbers )
1714{
1715 for ( auto pProject : AllProjects{} ) {
1716 if ( !GetProjectFrame( *pProject ).IsIconized() ) {
1718 bShowProjectNumbers ? pProject->GetProjectNumber() : -1 );
1719 }
1720 }
1721}
1722
1724 wxTopLevelWindow &window, AudacityProject &project )
1725{
1726 if( window.IsIconized() )
1727 window.Restore();
1728 window.Raise(); // May help identifying the window on Mac
1729
1730 // Construct this project's name and number.
1731 sProjName = project.GetProjectName();
1732 if ( sProjName.empty() ) {
1733 sProjName = _("<untitled>");
1734 UnnamedCount = std::count_if(
1736 []( const AllProjects::value_type &ptr ){
1737 return ptr->GetProjectName().empty();
1738 }
1739 );
1740 if ( UnnamedCount > 1 ) {
1741 sProjNumber.Printf(
1742 _("[Project %02i] "), project.GetProjectNumber() + 1 );
1743 RefreshAllTitles( true );
1744 }
1745 }
1746 else
1747 UnnamedCount = 0;
1748}
1749
1751 if( UnnamedCount > 1 )
1752 RefreshAllTitles( false );
1753}
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()
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()
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:3376
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void ZipClips(bool mustAlign=true)
Definition: WaveTrack.cpp:3317
double GetRate() const override
Definition: WaveTrack.cpp:821
auto Intervals()
Definition: WaveTrack.h:671
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)
FilePath FILES_API GetLongFileName(const FilePath &shortFileName)
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