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