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 (
804 !projectFileIO.WasCompacted() &&
806 {
807 // If compaction failed, we must do some work in case of close
808 // without save. Don't leave the document blob from the last
809 // push of undo history, when that undo state may get purged
810 // with deletion of some new sample blocks.
811 // REVIEW: UpdateSaved() might fail too. Do we need to test
812 // for that and report it?
813 projectFileIO.UpdateSaved(mLastSavedTracks.get());
814 }
815 }
816}
817
819{
820 auto &project = mProject;
821 auto &projectFileIO = ProjectFileIO::Get(project);
822
823 return projectFileIO.OpenProject();
824}
825
827{
828 auto &project = mProject;
829 auto &projectFileIO = ProjectFileIO::Get(project);
830
831 bool bOK = OpenProject();
832 if( !bOK )
833 {
834 auto tmpdir = wxFileName(TempDirectory::UnsavedProjectFileName()).GetPath();
835
836 UnwritableLocationErrorDialog dlg(nullptr, tmpdir);
837 dlg.ShowModal();
838 }
839 return bOK;
840}
841
843{
844 auto &project = mProject;
845 auto &projectFileIO = ProjectFileIO::Get(project);
846
847 projectFileIO.CloseProject();
848
849 // Blocks were locked in CompactProjectOnClose, so DELETE the data structure so that
850 // there's no memory leak.
852 {
853 mLastSavedTracks->Clear();
854 mLastSavedTracks.reset();
855 }
856}
857
858// static method, can be called outside of a project
860 const FileNames::FileType &extraType )
861{
862 // Construct the filter
863 const auto fileTypes = Importer::Get().GetFileTypes( extraType );
864
865 // Retrieve saved path
866 auto path = FileNames::FindDefaultPath(op);
867
868 // Construct and display the file dialog
869 wxArrayString selected;
870
871 FileDialogWrapper dlog(nullptr,
872 XO("Select one or more files"),
873 path,
874 wxT(""),
875 fileTypes,
876 wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
877
879
880 int dialogResult = dlog.ShowModal();
881
882 // Convert the filter index to type and save
883 auto index = dlog.GetFilterIndex();
884 const auto &saveType = fileTypes[ index ];
885
887 Importer::SetLastOpenType( saveType );
888
889 if (dialogResult == wxID_OK) {
890 // Return the selected files
891 dlog.GetPaths(selected);
892
893 // Remember the directory
894 FileNames::UpdateDefaultPath(op, ::wxPathOnly(dlog.GetPath()));
895 }
896
897 return selected;
898}
899
900// static method, can be called outside of a project
902{
903 const wxFileName newProjPathName(projPathName);
904 auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
905 iter = std::find_if( start, finish,
906 [&]( const AllProjects::value_type &ptr ){
907 return newProjPathName.SameAs(wxFileNameWrapper{ ProjectFileIO::Get(*ptr).GetFileName() });
908 } );
909 if (iter != finish) {
910 auto errMsg =
911 XO("%s is already open in another window.")
912 .Format( newProjPathName.GetName() );
913 wxLogError(errMsg.Translation()); //Debug?
915 errMsg,
916 XO("Error Opening Project"),
917 wxOK | wxCENTRE);
918 return true;
919 }
920 return false;
921}
922
924 const FilePath &fileNameArg, bool addtohistory)
925{
926 // On Win32, we may be given a short (DOS-compatible) file name on rare
927 // occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
928 // convert these to long file name first.
929 auto fileName = PlatformCompatibility::GetLongFileName(fileNameArg);
930
931 // Make sure it isn't already open.
932 // Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
933 // and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
934 // from, e.g., Win Explorer, it would bypass those, get to here with no check,
935 // then open a NEW project from the same data with no warning.
936 // This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
937 // but is not really part of that bug. Anyway, prevent it!
938 if (IsAlreadyOpen(fileName))
939 return nullptr;
940
941 // Data loss may occur if users mistakenly try to open ".aup3.bak" files
942 // left over from an unsuccessful save or by previous versions of Audacity.
943 // So we always refuse to open such files.
944 if (fileName.Lower().EndsWith(wxT(".aup3.bak")))
945 {
947 XO(
948"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."),
949 XO("Warning - Backup File Detected"),
950 wxOK | wxCENTRE,
951 nullptr);
952 return nullptr;
953 }
954
955 if (!::wxFileExists(fileName)) {
957 XO("Could not open file: %s").Format( fileName ),
958 XO("Error Opening File"),
959 wxOK | wxCENTRE,
960 nullptr);
961 return nullptr;
962 }
963
964 // Following block covers cases other than a project file:
965 {
966 wxFFile ff(fileName, wxT("rb"));
967
968 auto cleanup = finally([&]
969 {
970 if (ff.IsOpened())
971 {
972 ff.Close();
973 }
974 });
975
976 if (!ff.IsOpened()) {
978 XO("Could not open file: %s").Format( fileName ),
979 XO("Error opening file"),
980 wxOK | wxCENTRE,
981 nullptr);
982 return nullptr;
983 }
984
985 char buf[7];
986 auto numRead = ff.Read(buf, 6);
987 if (numRead != 6) {
989 XO("File may be invalid or corrupted: \n%s").Format( fileName ),
990 XO("Error Opening File or Project"),
991 wxOK | wxCENTRE,
992 nullptr);
993 return nullptr;
994 }
995
996 if (wxStrncmp(buf, "SQLite", 6) != 0)
997 {
998 // Not a database
999#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
1000 // Is it a plug-in?
1001 if (PluginManager::Get().DropFile(fileName)) {
1003 // Plug-in installation happened, not really opening of a file,
1004 // so return null
1005 return nullptr;
1006 }
1007#endif
1008 auto &project = chooser(false);
1009 // Undo history is incremented inside this:
1010 if (Get(project).Import(fileName))
1011 {
1012 // Undo history is incremented inside this:
1013 // Bug 2743: Don't zoom with lof.
1014 if (!fileName.AfterLast('.').IsSameAs(wxT("lof"), false))
1016 return &project;
1017 }
1018 return nullptr;
1019 }
1020 }
1021
1022 // Disallow opening of .aup3 project files from FAT drives, but only such
1023 // files, not importable types. (Bug 2800)
1025 XO("Project resides on FAT formatted drive.\n"
1026 "Copy it to another drive to open it.")))
1027 {
1028 return nullptr;
1029 }
1030
1031 auto &project = chooser(true);
1032 return Get(project).OpenProjectFile(fileName, addtohistory);
1033}
1034
1036 const std::function<void(const TranslatableString&)>& onError,
1037 const std::function<void(const TranslatableString&)>& onUnlink)
1038{
1039 // This is successively assigned the left member of each pair that
1040 // becomes unlinked
1041 Track::Holder unlinkedTrack;
1042 // Beware iterator invalidation, because stereo channels get zipped,
1043 // replacing WaveTracks
1044 for (auto iter = tracks.begin(); iter != tracks.end();) {
1045 auto t = (*iter++)->SharedPointer();
1046 const auto linkType = t->GetLinkType();
1047 // Note, the next function may have an important upgrading side effect,
1048 // and return no error; or it may find a real error and repair it, but
1049 // that repaired track won't be used because opening will fail.
1050 if (!t->LinkConsistencyFix()) {
1051 onError(XO("A channel of a stereo track was missing."));
1052 unlinkedTrack = nullptr;
1053 }
1054 if (!unlinkedTrack) {
1055 if (linkType != ChannelGroup::LinkType::None &&
1056 t->NChannels() == 1) {
1057 // The track became unlinked.
1058 // It should NOT have been replaced with a "zip"
1059 assert(t->GetOwner().get() == &tracks);
1060 // Wait until LinkConsistencyFix is called on the second track
1061 unlinkedTrack = t;
1062 // Fix the iterator, which skipped the right channel before the
1063 // unlinking
1064 iter = tracks.Find(t.get());
1065 ++iter;
1066 }
1067 }
1068 else {
1069 //Not an elegant way to deal with stereo wave track linking
1070 //compatibility between versions
1071 if (const auto left = dynamic_cast<WaveTrack*>(unlinkedTrack.get())) {
1072 if (const auto right = dynamic_cast<WaveTrack*>(t.get())) {
1073 // As with the left, it should not have vanished from the list
1074 assert(right->GetOwner().get() == &tracks);
1075 left->SetPan(-1.0f);
1076 right->SetPan(1.0f);
1079
1080 if(left->GetRate() != right->GetRate())
1081 //i18n-hint: explains why opened project was auto-modified
1082 onUnlink(XO("This project contained stereo tracks with different sample rates per channel."));
1083 if(left->GetSampleFormat() != right->GetSampleFormat())
1084 //i18n-hint: explains why opened project was auto-modified
1085 onUnlink(XO("This project contained stereo tracks with different sample formats in channels."));
1086 //i18n-hint: explains why opened project was auto-modified
1087 onUnlink(XO("This project contained stereo tracks with non-aligned content."));
1088 }
1089 }
1090 unlinkedTrack = nullptr;
1091 }
1092
1093 if (const auto message = t->GetErrorOpening()) {
1094 wxLogWarning(
1095 wxT("Track %s had error reading clip values from project file."),
1096 t->GetName());
1097 onError(*message);
1098 }
1099 }
1100}
1101
1103 const FilePath &fileName, bool addtohistory)
1104{
1105 // Allow extensions to update the project before opening it.
1108 return nullptr;
1109
1110 auto &project = mProject;
1111 auto &history = ProjectHistory::Get( project );
1112 auto &tracks = TrackList::Get( project );
1113 auto &trackPanel = TrackPanel::Get( project );
1114 auto &projectFileIO = ProjectFileIO::Get( project );
1115 auto &viewport = Viewport::Get( project );
1116
1117 auto results = ReadProjectFile( fileName );
1118 const bool bParseSuccess = results.parseSuccess;
1119 const auto &errorStr = results.errorString;
1120 const bool err = results.trackError;
1121
1122 if (bParseSuccess && !err) {
1124
1126 TrackFocus::Get(project).Set(*tracks.begin());
1127 viewport.HandleResize();
1128 trackPanel.Refresh(false);
1129
1130 // ? Old rationale in this comment no longer applies in 3.0.0, with no
1131 // more on-demand loading:
1132 trackPanel.Update(); // force any repaint to happen now,
1133 // else any asynch calls into the blockfile code will not have
1134 // finished logging errors (if any) before the call to ProjectFSCK()
1135
1136 if (addtohistory)
1137 FileHistory::Global().Append(fileName);
1138 }
1139
1140 if (bParseSuccess && !err) {
1141 if (projectFileIO.IsRecovered())
1142 {
1143 // PushState calls AutoSave(), so no longer need to do so here.
1144 history.PushState(XO("Project was recovered"), XO("Recover"));
1145 }
1146 return &project;
1147 }
1148 else {
1149 // Vaughan, 2011-10-30:
1150 // See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
1151 // Calling mTracks->Clear() with deleteTracks true results in data loss.
1152
1153 // PRL 2014-12-19:
1154 // I made many changes for wave track memory management, but only now
1155 // read the above comment. I may have invalidated the fix above (which
1156 // may have spared the files at the expense of leaked memory). But
1157 // here is a better way to accomplish the intent, doing like what happens
1158 // when the project closes:
1159 for (auto pTrack : tracks.Any<WaveTrack>())
1161
1162 tracks.Clear(); //tracks.Clear(true);
1163
1164 wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr.Debug());
1165
1166 projectFileIO.ShowError( *ProjectFramePlacement(&project),
1167 XO("Error Opening Project"),
1168 errorStr,
1169 results.helpUrl);
1170
1171 return nullptr;
1172 }
1173}
1174
1175void
1177 TrackHolders &&newTracks)
1178{
1179 auto &project = mProject;
1180 auto &history = ProjectHistory::Get( project );
1181 auto &projectFileIO = ProjectFileIO::Get( project );
1182 auto &tracks = TrackList::Get( project );
1183
1184 std::vector<Track*> results;
1185
1187
1188 wxFileName fn(fileName);
1189
1190 bool initiallyEmpty = tracks.empty();
1191 double newRate = 0;
1192 wxString trackNameBase = fn.GetName();
1193 int i = -1;
1194
1195 // Fix the bug 2109.
1196 // In case the project had soloed tracks before importing,
1197 // all newly imported tracks are muted.
1198 const bool projectHasSolo =
1199 !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
1200 if (projectHasSolo) {
1201 for (auto &group : newTracks)
1202 if (auto pTrack = dynamic_cast<PlayableTrack*>(group.get()))
1203 pTrack->SetMute(true);
1204 }
1205
1206 for (auto &group : newTracks) {
1207 if (auto pTrack = dynamic_cast<WaveTrack*>(group.get()))
1208 results.push_back(pTrack);
1209 tracks.Add(group);
1210 }
1211 newTracks.clear();
1212
1213 // Now name them
1214
1215 // Add numbers to track names only if there is more than one (mono or stereo)
1216 // track (not necessarily, more than one channel)
1217 const bool useSuffix = results.size() > 1;
1218
1219 for (const auto &newTrack : results) {
1220 ++i;
1221 newTrack->SetSelected(true);
1222 if (useSuffix)
1223 //i18n-hint Name default name assigned to a clip on track import
1224 newTrack->SetName(XC("%s %d", "clip name template")
1225 .Format(trackNameBase, i + 1).Translation());
1226 else
1227 newTrack->SetName(trackNameBase);
1228
1229 newTrack->TypeSwitch([&](WaveTrack &wt) {
1230 if (newRate == 0)
1231 newRate = wt.GetRate();
1232 const auto trackName = wt.GetName();
1233 for (const auto &interval : wt.Intervals())
1234 interval->SetName(trackName);
1235 });
1236 }
1237
1238 history.PushState(XO("Imported '%s'").Format( fileName ),
1239 XO("Import"));
1240
1241#if defined(__WXGTK__)
1242 // See bug #1224
1243 // The track panel hasn't been fully created, so ZoomFitHorizontally() will not give
1244 // expected results due to a window width of zero. Should be safe to yield here to
1245 // allow the creation to complete. If this becomes a problem, it "might" be possible
1246 // to queue a dummy event to trigger ZoomFitHorizontally().
1247 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
1248#endif
1249
1250 // If the project was clean and temporary (not permanently saved), then set
1251 // the filename to the just imported path.
1252 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1253 project.SetProjectName(fn.GetName());
1254 project.SetInitialImportPath(fn.GetPath());
1255 projectFileIO.SetProjectTitle();
1256 }
1257
1258 // Moved this call to higher levels to prevent flicker redrawing everything on each file.
1259 // HandleResize();
1260}
1261
1262namespace {
1263bool ImportProject(AudacityProject &dest, const FilePath &fileName)
1264{
1266 auto &project = temp.Project();
1267
1268 auto &projectFileIO = ProjectFileIO::Get(project);
1269 if (!projectFileIO.LoadProject(fileName, false))
1270 return false;
1271 auto &srcTracks = TrackList::Get(project);
1272 auto &destTracks = TrackList::Get(dest);
1273 for (const Track *pTrack : srcTracks)
1274 pTrack->PasteInto(dest, destTracks);
1276
1277 return true;
1278}
1279
1281 : public ImportProgressListener
1282{
1283 wxWeakRef<AudacityProject> mProject;
1284public:
1285
1287 : mProject(&project)
1288 {
1289
1290 }
1291
1292 bool OnImportFileOpened(ImportFileHandle& importFileHandle) override
1293 {
1294 mImportFileHandle = &importFileHandle;
1295 // File has more than one stream - display stream selector
1296 if (importFileHandle.GetStreamCount() > 1)
1297 {
1298 ImportStreamDialog ImportDlg(&importFileHandle, NULL, -1, XO("Select stream(s) to import"));
1299
1300 if (ImportDlg.ShowModal() == wxID_CANCEL)
1301 return false;
1302 }
1303 // One stream - import it by default
1304 else
1305 importFileHandle.SetStreamUsage(0,TRUE);
1306 return true;
1307 }
1308
1309 void OnImportProgress(double progress) override
1310 {
1311 constexpr double ProgressSteps { 1000.0 };
1312 if(!mProgressDialog)
1313 {
1314 wxFileName ff( mImportFileHandle->GetFilename() );
1315 auto title = XO("Importing %s").Format( mImportFileHandle->GetFileDescription() );
1316 mProgressDialog = BasicUI::MakeProgress(title, Verbatim(ff.GetFullName()));
1317 }
1318 auto result = mProgressDialog->Poll(progress * ProgressSteps, ProgressSteps);
1320 mImportFileHandle->Cancel();
1321 else if(result == BasicUI::ProgressResult::Stopped)
1322 mImportFileHandle->Stop();
1323 }
1324
1325 void OnImportResult(ImportResult result) override
1326 {
1327 mProgressDialog.reset();
1328 if(result == ImportResult::Error)
1329 {
1330 auto message = mImportFileHandle->GetErrorMessage();
1331 if(!message.empty())
1332 {
1333 AudacityMessageBox(message, XO("Import"), wxOK | wxCENTRE | wxICON_ERROR,
1334 mProject ? &GetProjectFrame(*mProject) : nullptr);
1335 }
1336 }
1337 }
1338
1339private:
1340
1341 ImportFileHandle* mImportFileHandle {nullptr};
1342 std::unique_ptr<BasicUI::ProgressDialog> mProgressDialog;
1343};
1344} // namespace
1345
1346bool ProjectFileManager::Import(const FilePath& fileName, bool addToHistory)
1347{
1348 wxArrayString fileNames;
1349 fileNames.Add(fileName);
1350 return Import(std::move(fileNames), addToHistory);
1351}
1352
1353bool ProjectFileManager::Import(wxArrayString fileNames, bool addToHistory)
1354{
1355 fileNames.Sort(FileNames::CompareNoCase);
1357 std::vector<wxString> { fileNames.begin(), fileNames.end() },
1358 addToHistory))
1359 return false;
1360 // Last track in the project is the one that was just added. Use it for
1361 // focus, etc.
1362 Track* lastTrack = nullptr;
1363 const auto range = TrackList::Get(mProject).Any<Track>();
1364 assert(!range.empty());
1365 if(range.empty())
1366 return false;
1367 lastTrack = *(range.rbegin());
1368 BasicUI::CallAfter([wTrack = lastTrack->weak_from_this(), wProject = mProject.weak_from_this()] {
1369 const auto project = wProject.lock();
1370 const auto track = wTrack.lock();
1371 if (!project || !track)
1372 return;
1373 auto& viewPort = Viewport::Get(*project);
1374 TrackFocus::Get(*project).Set(track.get(), true);
1375 viewPort.ZoomFitHorizontally();
1376 viewPort.ShowTrack(*track);
1377 viewPort.HandleResize(); // Adjust scrollers for NEW track sizes.
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()
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: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