11namespace tracktion {
inline namespace engine
15static const char* magicNumberV1 =
"TP01";
18Project::Project (
Engine& e, ProjectManager& pm,
const juce::File& projectFile)
19 : engine (e), projectManager (pm), file (projectFile)
21 jassert (isTracktionProjectFile (file));
23 for (
auto* p : projectManager.openProjects)
26 jassert (p->getProjectFile() != file);
29 projectManager.openProjects.add (
this);
37 projectManager.openProjects.removeFirstMatchingValue (
this);
39 notifyListenersOfDeletion();
42void Project::lockFile()
44 if (fileLockingStream ==
nullptr)
45 fileLockingStream = file.createInputStream();
48void Project::unlockFile()
50 fileLockingStream.reset();
56 readOnly = ! (file.hasWriteAccess() && ! file.isDirectory());
59 auto in = getInputStream();
61 if (in !=
nullptr && readProjectHeader (*in))
63 in->setPosition (objectOffset);
64 int num = in->readInt();
66 jassert (num >= 0 && num < 20000);
73 o.itemID = in->readInt();
74 o.fileOffset = in->readInt();
79 if (o.fileOffset > 0 && o.itemID != 0)
93void Project::refreshProjectPropertiesFromFile()
97 if (
auto in = getInputStream())
98 readProjectHeader (*in,
false);
111 if (strncmp (n, magicNumberV1, 4) == 0)
120 while (--numProps >= 0)
126 in.
read (mem.getData(), size);
128 properties.set (propName, mem.toString());
131 return objectOffset > 0 && indexOffset > 0;
137bool Project::loadProjectItem (ObjectInfo& o)
139 if (o.fileOffset > 0)
141 if (
auto in = getInputStream())
144 o.item =
new ProjectItem (engine, ProjectItemID (o.itemID, projectId), in);
153void Project::loadAllProjectItems()
158 for (
auto& o : objects)
159 if (o.item == nullptr)
160 if (! loadProjectItem (o))
166 if (stream ==
nullptr && file.getSize() > 0)
167 if (
auto in = file.createInputStream())
173void Project::handleAsyncUpdate()
182 if (isValid() && ! isReadOnly())
189 loadAllProjectItems();
191 auto tempFile = file.getParentDirectory().getNonexistentChildFile (
"temp",
".tmp");
193 if (
auto out = tempFile.createOutputStream())
202 if (tempFile.moveFileTo (file) || tempFile.moveFileTo (file))
207 +
" Saved: " + file.getFullPathName());
214 bool b = tempFile.deleteFile();
217 DBG (
"!!couldn't save " + file.getFullPathName());
223 cancelPendingUpdate();
236 out.
write (magicNumberV1, 4);
242 for (
int i = 0; i < properties.size(); ++i)
244 out.
writeString (properties.getName(i).toString());
246 auto value = properties.getValueAt (i).
toString();
247 auto utf8 = value.toUTF8();
248 auto numBytes = value.getNumBytesAsUTF8() + 1;
251 out.
write (utf8, numBytes);
254 for (
auto& o : objects)
259 c->writeToStream (out);
267 for (
auto& o : objects)
274 ProjectSearchIndex searchIndex (*
this);
276 for (
auto& o : objects)
278 searchIndex.addClip (c);
280 searchIndex.writeToStream (out);
288bool Project::isValid()
const
290 return projectId != 0;
293bool Project::isReadOnly()
const
298int Project::getProjectID()
const
306 return properties [name];
312 properties.set (name, value);
318 return getProjectProperty (
"name");
323 return getProjectProperty (
"description");
330 setProjectProperty (
"name", newName.
substring (0, 64));
331 engine.getUIBehaviour().updateAllProjectItemLists();
334 + file.getFileExtension());
339 if (file.moveFileTo (dst) || file.moveFileTo (dst))
342 projectManager.updateProjectFile (*
this, file);
345 projectManager.saveList();
349void Project::setDescription (
const juce::String& newDesc)
351 setProjectProperty (
"description",
juce::String (newDesc).substring (0, 512));
354void Project::createNewProjectId()
358 while (projectManager.getProject (newID))
368void Project::redirectIDsFromProject (
int oldProjId,
int newProjId)
370 for (
int k = 0; k < getNumProjectItems(); ++k)
372 if (
auto mo = getProjectItemAt (k))
378 for (
auto exportable : Exportable::addAllExportables (*ed))
380 for (
auto& item : exportable->getReferencedItems())
382 if (item.itemID.getProjectID() == oldProjId)
383 exportable->reassignReferencedItem (item, item.itemID.withNewProjectID (newProjId), 0.0);
395bool Project::isLibraryProject()
const
397 return projectManager.findFolderContaining (*
this) == projectManager.getLibraryProjectsFolder();
400void Project::changed()
403 triggerAsyncUpdate();
404 Selectable::changed();
407int Project::getNumProjectItems()
409 return objects.size();
412ProjectItemID Project::getProjectItemID (
int i)
417 return ProjectItemID (objects.getReference(i).itemID, projectId);
426 for (
auto& o : objects)
438 for (
auto& o : objects)
439 dest.add (ProjectItemID (o.itemID, projectId));
444ProjectItem::Ptr Project::getProjectItemAt (
int i)
450 auto& o = objects.getReference(i);
452 if (o.item ==
nullptr)
467 for (
auto& o : objects)
469 if (o.item ==
nullptr)
478ProjectItem::Ptr Project::getProjectItemForID (ProjectItemID targetId)
481 return getProjectItemAt (getIndexOf (targetId));
484ProjectItem::Ptr Project::getProjectItemForFile (
const juce::File& fileToFind)
488 for (
auto& o : objects)
490 if (o.item ==
nullptr)
491 if (! loadProjectItem (o))
494 if (o.item->isForFile (fileToFind))
501int Project::getIndexOf (ProjectItemID mo)
const
505 if (mo.getProjectID() == getProjectID())
507 auto itemID = mo.getItemID();
509 for (
int i = objects.size(); --i >= 0;)
510 if (objects.getReference(i).itemID == itemID)
517void Project::moveProjectItem (
int indexToMoveFrom,
int indexToMoveTo)
519 if (indexToMoveTo != indexToMoveFrom)
523 if (indexToMoveFrom >= 0 && indexToMoveFrom < objects.size())
525 objects.move (indexToMoveFrom,
juce::jlimit (0, objects.size(), indexToMoveTo));
535 const ProjectItem::Category cat,
540 if (isValid() && ! isReadOnly())
542 if (
auto mo = getProjectItemForFile (fileToReference))
543 if (mo->getID().isValid() && mo->getType() == type)
547 o.item =
new ProjectItem (engine, name, type, description, {}, cat, 0,
548 ProjectItemID::createNewID (getProjectID()));
556 objects.insert (0, o);
561 o.item->setSourceFile (fileToReference);
562 o.item->verifyLength();
575 const ProjectItem::Category cat,
579 o.item =
new ProjectItem (engine, name, type, description, {}, cat, 0, newID);
582 o.item->file = relPathName;
593bool Project::removeProjectItem (ProjectItemID item,
bool deleteSourceMaterial)
595 if (isValid() && ! isReadOnly())
600 const int index = getIndexOf (item);
605 auto& o = objects.getReference (index);
607 if (o.item !=
nullptr)
611 if (deleteSourceMaterial)
612 if (! o.item->deleteSourceFile())
616 objects.remove (index);
627juce::File Project::getDirectoryForMedia (ProjectItem::Category category)
const
629 auto dir = getDefaultDirectory();
633 case ProjectItem::Category::archives: dir = dir.getChildFile (
TRANS(
"Archived"));
break;
634 case ProjectItem::Category::exports: dir = dir.getChildFile (
TRANS(
"Exported"));
break;
635 case ProjectItem::Category::frozen: dir = dir.getChildFile (
TRANS(
"Frozen"));
break;
636 case ProjectItem::Category::imported: dir = dir.getChildFile (
TRANS(
"Imported"));
break;
637 case ProjectItem::Category::recorded: dir = dir.getChildFile (
TRANS(
"Recorded"));
break;
638 case ProjectItem::Category::rendered: dir = dir.getChildFile (
TRANS(
"Rendered"));
break;
639 case ProjectItem::Category::video: dir = dir.getChildFile (
TRANS(
"Movies"));
break;
641 case ProjectItem::Category::edit:
642 case ProjectItem::Category::none:
646 if (! dir.isDirectory())
647 dir.createDirectory();
652juce::File Project::getDefaultDirectory()
const
654 return file.getParentDirectory();
657ProjectItem::Ptr Project::createNewEdit()
661 for (
int i = 0; i < getNumProjectItems(); ++i)
663 if (
auto p = getProjectItemAt (i))
667 auto nm = p->getName();
669 if (nm.startsWithIgnoreCase (
getName() +
" Edit "))
670 maxSuffix =
std::max (maxSuffix, nm.getTrailingIntValue());
675 auto name =
getName() +
" Edit ";
676 name << (maxSuffix + 1);
678 auto f = getDefaultDirectory().getNonexistentChildFile (name, editFileSuffix,
false);
681 return createNewItem (f, ProjectItem::editItemType(), name,
682 {}, ProjectItem::Category::edit,
true);
698 if (
auto in = getInputStream())
701 psi.readFromStream (*in);
705 psi.findMatches (searchOp, results);
709void Project::mergeArchiveContents (
const juce::File& archiveFile)
713 if (! archive.isValidArchive())
715 engine.getUIBehaviour().showWarningMessage (
TRANS(
"This file wasn't a valid tracktion archive file"));
722 if (archive.extractAllAsTask (getProjectFile().getParentDirectory(),
true, newFiles, wasAborted))
726 for (
const auto& f : newFiles)
728 if (isTracktionProjectFile (f))
730 mergeOtherProjectIntoThis (f);
737 refreshFolderStructure();
742 engine.getUIBehaviour().showWarningMessage (
TRANS(
"Errors occurred whilst trying to unpack this archive"));
746void Project::mergeOtherProjectIntoThis (
const juce::File& f)
748 ProjectManager::TempProject temp (projectManager, f,
false);
750 if (
auto p = temp.project)
754 for (
int i = 0; i < p->getNumProjectItems(); ++i)
756 if (
auto src = p->getProjectItemAt (i))
758 if (
auto mo = quickAddProjectItem (src->file,
765 mo->copyAllPropertiesFrom (*src);
767 mo->changeProjectId (p->getProjectID(), getProjectID());
779 auto unreffed = getAllProjectItemIDs();
781 for (
int j = 0; j < getNumProjectItems(); ++j)
783 if (unreffed.isEmpty())
786 if (
auto mo = getProjectItemAt (j))
792 for (
int i = unreffed.size(); --i >= 0;)
793 if (referencesProjectItem (*ed, unreffed.getReference(i)))
804 return isReadOnly() ?
TRANS(
"Read-Only Project")
808bool Project::askAboutTempoDetect (
const juce::File& f,
bool& shouldSetAutoTempo)
const
810 #if JUCE_MODAL_LOOPS_PERMITTED
811 NagMode im = (NagMode)
static_cast<int> (engine.getPropertyStorage().getProperty (SettingID::autoTempoDetect, (
int) nagAsk));
813 shouldSetAutoTempo = engine.getPropertyStorage().getProperty (SettingID::autoTempoMatch,
false);
815 if (im == nagAutoYes)
823 autoTempo.setSize (200, 20);
826 dontAsk.setSize (200, 20);
829 .createAlertWindow (
TRANS(
"Detect Tempo?"),
830 TRANS(
"No tempo information was found in XZZX, would you like to detect it automatically?")
832 {}, {}, {}, juce::AlertWindow::QuestionIcon, 0,
nullptr));
834 w->addCustomComponent (&autoTempo);
835 w->addCustomComponent (&dontAsk);
839 const int res = w->runModalLoop();
841 shouldSetAutoTempo = autoTempo.getToggleState();
842 engine.getPropertyStorage().setProperty (SettingID::autoTempoMatch, shouldSetAutoTempo);
844 if (dontAsk.getToggleState())
845 engine.getPropertyStorage().setProperty (SettingID::autoTempoDetect, (
int) (res == 1 ? nagAutoYes : nagAutoNo));
854void Project::ensureFolderCreated (ProjectItem::Category c)
856 auto dir = getDirectoryForMedia (c);
858 if (! dir.isDirectory())
859 dir.createDirectory();
862void Project::createDefaultFolders()
864 ensureFolderCreated (ProjectItem::Category::archives);
865 ensureFolderCreated (ProjectItem::Category::exports);
866 ensureFolderCreated (ProjectItem::Category::imported);
867 ensureFolderCreated (ProjectItem::Category::recorded);
868 ensureFolderCreated (ProjectItem::Category::rendered);
869 ensureFolderCreated (ProjectItem::Category::edit);
872void Project::refreshFolderStructure()
874 auto projDir = getProjectFile().getParentDirectory();
876 for (
auto& item : getAllProjectItemIDs())
878 if (
auto mo = getProjectItemForID (item))
880 auto srcFile = mo->getSourceFile();
881 auto dstDir = getDirectoryForMedia (mo->getCategory());
883 if (! dstDir.isDirectory())
884 dstDir.createDirectory();
886 if (srcFile.isAChildOf (projDir))
888 auto dstFile = dstDir.getChildFile (srcFile.getFileName());
890 if (srcFile.moveFileTo (dstFile))
891 mo->setSourceFile (dstFile);
void add(const ElementType &newElement)
bool write(const void *, size_t) override
bool setPosition(int64) override
int64 getPosition() override
String getFileNameWithoutExtension() const
static String createLegalFileName(const String &fileNameToFix)
bool isValid() const noexcept
const String & toString() const noexcept
static const int escapeKey
static const int returnKey
static LookAndFeel & getDefaultLookAndFeel() noexcept
virtual bool writeInt(int value)
virtual bool writeString(const String &text)
static Random & getSystemRandom() noexcept
String substring(int startIndex, int endIndex) const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Contains methods for saving an Edit to a file.
An ID representing one of the items in a Project.
int getItemID() const
Returns the ID of the item within the project.
Represents a file-based resource that is used in a project.
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
void ignoreUnused(Types &&...) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
std::unique_ptr< Edit > loadEditForExamining(ProjectManager &pm, ProjectItemID itemID, Edit::EditRole role)
Uses the ProjectManager to find an Edit file and open it.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.