BsProjectLibrary.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. #include "BsProjectLibrary.h"
  2. #include "BsEditorApplication.h"
  3. #include "CmPath.h"
  4. #include "CmFileSystem.h"
  5. #include "CmException.h"
  6. #include "CmResources.h"
  7. #include "CmResourceManifest.h"
  8. #include "CmImporter.h"
  9. #include "BsResourceMeta.h"
  10. #include "CmResources.h"
  11. #include "CmImporter.h"
  12. #include "CmImportOptions.h"
  13. #include "CmFileSerializer.h"
  14. #include "CmFolderMonitor.h"
  15. #include "CmDebug.h"
  16. using namespace CamelotFramework;
  17. using namespace BansheeEngine;
  18. namespace BansheeEditor
  19. {
  20. const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal\\Resources";
  21. ProjectLibrary::LibraryEntry::LibraryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent, LibraryEntryType type)
  22. :path(path), parent(parent), type(type), elementName(name)
  23. { }
  24. ProjectLibrary::ResourceEntry::ResourceEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent)
  25. :LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0)
  26. { }
  27. ProjectLibrary::DirectoryEntry::DirectoryEntry(const CM::WPath& path, const CM::WString& name, DirectoryEntry* parent)
  28. :LibraryEntry(path, name, parent, LibraryEntryType::Directory)
  29. { }
  30. ProjectLibrary::ProjectLibrary()
  31. :mRootEntry(nullptr)
  32. {
  33. mMonitor = cm_new<FolderMonitor>();
  34. FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName |
  35. (UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
  36. mMonitor->startMonitor(EditorApplication::instance().getResourcesFolderPath(), true, folderChanges);
  37. mMonitor->onAdded.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
  38. mMonitor->onRemoved.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
  39. mMonitor->onModified.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
  40. // TODO - Load
  41. checkForModifications(EditorApplication::instance().getResourcesFolderPath());
  42. }
  43. ProjectLibrary::~ProjectLibrary()
  44. {
  45. // TODO - Save
  46. mMonitor->stopMonitorAll();
  47. cm_delete(mMonitor);
  48. if(mRootEntry != nullptr)
  49. deleteDirectoryInternal(mRootEntry);
  50. }
  51. void ProjectLibrary::update()
  52. {
  53. mMonitor->update();
  54. }
  55. void ProjectLibrary::checkForModifications(const CM::WString& fullPath)
  56. {
  57. if(!PathUtil::includes(fullPath, EditorApplication::instance().getResourcesFolderPath()))
  58. return; // Folder not part of our resources path, so no modifications
  59. if(mRootEntry == nullptr)
  60. {
  61. WPath resPath = toPath(EditorApplication::instance().getResourcesFolderPath());
  62. mRootEntry = cm_new<DirectoryEntry>(resPath, toWString(PathUtil::getFilename(resPath)), nullptr);
  63. }
  64. WPath pathToSearch = toPath(fullPath);
  65. LibraryEntry* entry = findEntry(pathToSearch);
  66. if(entry == nullptr) // File could be new, try to find parent directory entry
  67. {
  68. WPath parentDirPath = PathUtil::parentPath(pathToSearch);
  69. entry = findEntry(parentDirPath);
  70. // Cannot find parent directory. Create the needed hierarchy.
  71. DirectoryEntry* entryParent = nullptr;
  72. DirectoryEntry* newHierarchyParent = nullptr;
  73. if(entry == nullptr)
  74. createInternalParentHierarchy(pathToSearch, &newHierarchyParent, &entryParent);
  75. else
  76. entryParent = static_cast<DirectoryEntry*>(entry);
  77. if(FileSystem::isFile(pathToSearch))
  78. {
  79. addResourceInternal(entryParent, pathToSearch);
  80. }
  81. else if(FileSystem::isDirectory(pathToSearch))
  82. {
  83. addDirectoryInternal(entryParent, pathToSearch);
  84. if(newHierarchyParent == nullptr)
  85. checkForModifications(toWString(pathToSearch));
  86. }
  87. if(newHierarchyParent != nullptr)
  88. checkForModifications(toWString(newHierarchyParent->path));
  89. }
  90. else if(entry->type == LibraryEntryType::File)
  91. {
  92. if(FileSystem::isFile(entry->path))
  93. {
  94. reimportResourceInternal(static_cast<ResourceEntry*>(entry));
  95. }
  96. else
  97. {
  98. deleteResourceInternal(static_cast<ResourceEntry*>(entry));
  99. }
  100. }
  101. else if(entry->type == LibraryEntryType::Directory) // Check folder and all subfolders for modifications
  102. {
  103. if(!FileSystem::isDirectory(entry->path))
  104. {
  105. deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
  106. }
  107. else
  108. {
  109. Stack<DirectoryEntry*>::type todo;
  110. todo.push(static_cast<DirectoryEntry*>(entry));
  111. Vector<WPath>::type childFiles;
  112. Vector<WPath>::type childDirectories;
  113. Vector<bool>::type existingEntries;
  114. Vector<LibraryEntry*>::type toDelete;
  115. while(!todo.empty())
  116. {
  117. DirectoryEntry* currentDir = todo.top();
  118. todo.pop();
  119. existingEntries.clear();
  120. existingEntries.resize(currentDir->mChildren.size());
  121. for(UINT32 i = 0; i < (UINT32)currentDir->mChildren.size(); i++)
  122. existingEntries[i] = false;
  123. childFiles.clear();
  124. childDirectories.clear();
  125. FileSystem::getChildren(currentDir->path, childFiles, childDirectories);
  126. for(auto& filePath : childFiles)
  127. {
  128. if(isMeta(filePath))
  129. {
  130. WPath sourceFilePath = filePath;
  131. PathUtil::replaceExtension(sourceFilePath, L"");
  132. if(FileSystem::isFile(sourceFilePath))
  133. {
  134. LOGWRN("Found a .meta file without a corresponding resource. Deleting.");
  135. FileSystem::remove(filePath);
  136. }
  137. }
  138. else
  139. {
  140. ResourceEntry* existingEntry = nullptr;
  141. UINT32 idx = 0;
  142. for(auto& child : currentDir->mChildren)
  143. {
  144. if(child->type == LibraryEntryType::File && child->path == filePath)
  145. {
  146. existingEntries[idx] = true;
  147. existingEntry = static_cast<ResourceEntry*>(child);
  148. break;
  149. }
  150. idx++;
  151. }
  152. if(existingEntry != nullptr)
  153. {
  154. reimportResourceInternal(existingEntry);
  155. }
  156. else
  157. {
  158. addResourceInternal(currentDir, filePath);
  159. }
  160. }
  161. }
  162. for(auto& dirPath : childDirectories)
  163. {
  164. DirectoryEntry* existingEntry = nullptr;
  165. UINT32 idx = 0;
  166. for(auto& child : currentDir->mChildren)
  167. {
  168. if(child->type == LibraryEntryType::Directory && child->path == dirPath)
  169. {
  170. existingEntries[idx] = true;
  171. existingEntry = static_cast<DirectoryEntry*>(child);
  172. break;
  173. }
  174. idx++;
  175. }
  176. if(existingEntry == nullptr)
  177. addDirectoryInternal(currentDir, dirPath);
  178. }
  179. {
  180. for(UINT32 i = 0; i < (UINT32)existingEntries.size(); i++)
  181. {
  182. if(existingEntries[i])
  183. continue;
  184. toDelete.push_back(currentDir->mChildren[i]);
  185. }
  186. for(auto& child : toDelete)
  187. {
  188. if(child->type == LibraryEntryType::Directory)
  189. deleteDirectoryInternal(static_cast<DirectoryEntry*>(child));
  190. else if(child->type == LibraryEntryType::File)
  191. deleteResourceInternal(static_cast<ResourceEntry*>(child));
  192. }
  193. }
  194. for(auto& child : currentDir->mChildren)
  195. {
  196. if(child->type == LibraryEntryType::Directory)
  197. todo.push(static_cast<DirectoryEntry*>(child));
  198. }
  199. }
  200. }
  201. }
  202. }
  203. ProjectLibrary::ResourceEntry* ProjectLibrary::addResourceInternal(DirectoryEntry* parent, const CM::WPath& filePath)
  204. {
  205. ResourceEntry* newResource = cm_new<ResourceEntry>(filePath, toWString(PathUtil::getFilename(filePath)), parent);
  206. parent->mChildren.push_back(newResource);
  207. reimportResourceInternal(newResource);
  208. if(!onEntryAdded.empty())
  209. onEntryAdded(filePath);
  210. return newResource;
  211. }
  212. ProjectLibrary::DirectoryEntry* ProjectLibrary::addDirectoryInternal(DirectoryEntry* parent, const CM::WPath& dirPath)
  213. {
  214. DirectoryEntry* newEntry = cm_new<DirectoryEntry>(dirPath, toWString(PathUtil::getFilename(dirPath)), parent);
  215. parent->mChildren.push_back(newEntry);
  216. if(!onEntryAdded.empty())
  217. onEntryAdded(dirPath);
  218. return newEntry;
  219. }
  220. void ProjectLibrary::deleteResourceInternal(ResourceEntry* resource)
  221. {
  222. ResourceManifestPtr manifest = gResources().getResourceManifest();
  223. if(resource->meta != nullptr)
  224. {
  225. if(manifest->uuidExists(resource->meta->getUUID()))
  226. {
  227. const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
  228. if(FileSystem::isFile(path))
  229. FileSystem::remove(path);
  230. manifest->unregisterResource(resource->meta->getUUID());
  231. }
  232. }
  233. DirectoryEntry* parent = resource->parent;
  234. auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(),
  235. [&] (const LibraryEntry* entry) { return entry == resource; });
  236. parent->mChildren.erase(findIter);
  237. if(!onEntryRemoved.empty())
  238. onEntryRemoved(resource->path);
  239. cm_delete(resource);
  240. }
  241. void ProjectLibrary::deleteDirectoryInternal(DirectoryEntry* directory)
  242. {
  243. if(directory == mRootEntry)
  244. mRootEntry = nullptr;
  245. for(auto& child : directory->mChildren)
  246. {
  247. if(child->type == LibraryEntryType::Directory)
  248. deleteDirectoryInternal(static_cast<DirectoryEntry*>(child));
  249. else
  250. deleteResourceInternal(static_cast<ResourceEntry*>(child));
  251. }
  252. DirectoryEntry* parent = directory->parent;
  253. auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(),
  254. [&] (const LibraryEntry* entry) { return entry == directory; });
  255. parent->mChildren.erase(findIter);
  256. if(!onEntryRemoved.empty())
  257. onEntryRemoved(directory->path);
  258. cm_delete(directory);
  259. }
  260. void ProjectLibrary::reimportResourceInternal(ResourceEntry* resource)
  261. {
  262. WString ext = toWString(PathUtil::getExtension(resource->path));
  263. WPath metaPath = resource->path;
  264. PathUtil::replaceExtension(metaPath, ext + L".meta");
  265. ext = ext.substr(1, ext.size() - 1); // Remove the .
  266. if(!Importer::instance().supportsFileType(ext))
  267. return;
  268. if(resource->meta == nullptr)
  269. {
  270. FileSerializer fs;
  271. if(FileSystem::isFile(metaPath))
  272. {
  273. std::shared_ptr<IReflectable> loadedMeta = fs.decode(toWString(metaPath));
  274. if(loadedMeta != nullptr && loadedMeta->isDerivedFrom(ResourceMeta::getRTTIStatic()))
  275. {
  276. ResourceMetaPtr resourceMeta = std::static_pointer_cast<ResourceMeta>(loadedMeta);
  277. resource->meta = resourceMeta;
  278. }
  279. }
  280. }
  281. if(!isUpToDate(resource))
  282. {
  283. ImportOptionsPtr importOptions = nullptr;
  284. if(resource->meta != nullptr)
  285. importOptions = resource->meta->getImportOptions();
  286. else
  287. importOptions = Importer::instance().createImportOptions(toWString(resource->path));
  288. HResource importedResource = Importer::instance().import(toWString(resource->path), importOptions);
  289. if(resource->meta == nullptr)
  290. {
  291. resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions);
  292. FileSerializer fs;
  293. fs.encode(resource->meta.get(), toWString(metaPath));
  294. }
  295. WPath internalResourcesPath = toPath(EditorApplication::instance().getActiveProjectPath()) / toPath(INTERNAL_RESOURCES_DIR);
  296. if(!FileSystem::isDirectory(internalResourcesPath))
  297. FileSystem::createDir(internalResourcesPath);
  298. internalResourcesPath /= toPath(toWString(importedResource.getUUID()) + L".asset");
  299. gResources().save(importedResource, toWString(internalResourcesPath), true);
  300. ResourceManifestPtr manifest = gResources().getResourceManifest();
  301. manifest->registerResource(importedResource.getUUID(), toWString(internalResourcesPath));
  302. resource->lastUpdateTime = std::time(nullptr);
  303. }
  304. }
  305. bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const
  306. {
  307. if(resource->meta == nullptr)
  308. return false;
  309. ResourceManifestPtr manifest = gResources().getResourceManifest();
  310. if(!manifest->uuidExists(resource->meta->getUUID()))
  311. return false;
  312. const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
  313. if(!FileSystem::isFile(path))
  314. return false;
  315. std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path);
  316. return lastModifiedTime <= resource->lastUpdateTime;
  317. }
  318. ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const CM::WPath& fullPath) const
  319. {
  320. Vector<WString>::type pathElems = PathUtil::split(toWString(fullPath));
  321. Vector<WString>::type rootElems = PathUtil::split(toWString(mRootEntry->path));
  322. auto pathIter = pathElems.begin();
  323. auto rootIter = rootElems.begin();
  324. while(pathIter != pathElems.end() && rootIter != rootElems.end() && PathUtil::comparePathElements(*pathIter, *rootIter))
  325. {
  326. ++pathIter;
  327. ++rootIter;
  328. }
  329. if(pathIter == pathElems.begin()) // Not a single entry matches Resources path
  330. return nullptr;
  331. --pathIter;
  332. Stack<LibraryEntry*>::type todo;
  333. todo.push(mRootEntry);
  334. while(!todo.empty())
  335. {
  336. LibraryEntry* current = todo.top();
  337. todo.pop();
  338. if(PathUtil::comparePathElements(*pathIter, current->elementName))
  339. {
  340. ++pathIter;
  341. if(pathIter == pathElems.end())
  342. return current;
  343. while(!todo.empty())
  344. todo.pop();
  345. if(current->type == LibraryEntryType::Directory)
  346. {
  347. DirectoryEntry* dirEntry = static_cast<DirectoryEntry*>(current);
  348. for(auto& child : dirEntry->mChildren)
  349. todo.push(child);
  350. }
  351. }
  352. }
  353. return nullptr;
  354. }
  355. void ProjectLibrary::moveEntry(const CM::WPath& oldPath, const CM::WPath& newPath)
  356. {
  357. if(FileSystem::isFile(oldPath) || FileSystem::isDirectory(oldPath))
  358. FileSystem::move(oldPath, newPath);
  359. WPath oldMetaPath = getMetaPath(oldPath);
  360. WPath newMetaPath = getMetaPath(newPath);
  361. LibraryEntry* oldEntry = findEntry(oldPath);
  362. if(oldEntry != nullptr) // Moving from the Resources folder
  363. {
  364. // Moved outside of Resources, delete entry & meta file
  365. if(!PathUtil::includes(toWString(newPath), EditorApplication::instance().getResourcesFolderPath()))
  366. {
  367. if(oldEntry->type == LibraryEntryType::File)
  368. {
  369. deleteResourceInternal(static_cast<ResourceEntry*>(oldEntry));
  370. if(FileSystem::isFile(oldMetaPath))
  371. FileSystem::remove(oldMetaPath);
  372. }
  373. else if(oldEntry->type == LibraryEntryType::Directory)
  374. deleteDirectoryInternal(static_cast<DirectoryEntry*>(oldEntry));
  375. }
  376. else // Just moving internally
  377. {
  378. if(FileSystem::isFile(oldMetaPath))
  379. FileSystem::move(oldMetaPath, newMetaPath);
  380. DirectoryEntry* parent = oldEntry->parent;
  381. auto findIter = std::find(parent->mChildren.begin(), parent->mChildren.end(), oldEntry);
  382. if(findIter != parent->mChildren.end())
  383. parent->mChildren.erase(findIter);
  384. WPath parentPath = PathUtil::parentPath(newPath);
  385. DirectoryEntry* newEntryParent = nullptr;
  386. LibraryEntry* newEntryParentLib = findEntry(parentPath);
  387. if(newEntryParentLib != nullptr)
  388. {
  389. assert(newEntryParentLib->type == LibraryEntryType::Directory);
  390. newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
  391. }
  392. DirectoryEntry* newHierarchyParent = nullptr;
  393. if(newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy
  394. createInternalParentHierarchy(newPath, &newHierarchyParent, &newEntryParent);
  395. newEntryParent->mChildren.push_back(oldEntry);
  396. oldEntry->parent = newEntryParent;
  397. oldEntry->path = newPath;
  398. oldEntry->elementName = toWString(PathUtil::getFilename(newPath));
  399. if(!onEntryRemoved.empty())
  400. onEntryRemoved(oldPath);
  401. if(!onEntryAdded.empty())
  402. onEntryAdded(newPath);
  403. if(newHierarchyParent != nullptr)
  404. checkForModifications(toWString(newHierarchyParent->path));
  405. }
  406. }
  407. else // Moving from outside of the Resources folder (likely adding a new resource)
  408. {
  409. checkForModifications(toWString(newPath));
  410. }
  411. }
  412. void ProjectLibrary::deleteEntry(const CM::WPath& path)
  413. {
  414. if(FileSystem::isFile(path))
  415. FileSystem::remove(path);
  416. LibraryEntry* entry = findEntry(path);
  417. if(entry != nullptr)
  418. {
  419. if(entry->type == LibraryEntryType::File)
  420. {
  421. deleteResourceInternal(static_cast<ResourceEntry*>(entry));
  422. WPath metaPath = getMetaPath(path);
  423. if(FileSystem::isFile(metaPath))
  424. FileSystem::remove(metaPath);
  425. }
  426. else if(entry->type == LibraryEntryType::Directory)
  427. deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
  428. }
  429. }
  430. void ProjectLibrary::createInternalParentHierarchy(const CM::WPath& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
  431. {
  432. WPath parentPath = fullPath;
  433. DirectoryEntry* newEntryParent = nullptr;
  434. Stack<WPath>::type parentPaths;
  435. do
  436. {
  437. WPath newParentPath = PathUtil::parentPath(parentPath);
  438. if(newParentPath == parentPath)
  439. break;
  440. LibraryEntry* newEntryParentLib = findEntry(newParentPath);
  441. if(newEntryParentLib != nullptr)
  442. {
  443. assert(newEntryParentLib->type == LibraryEntryType::Directory);
  444. newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
  445. break;
  446. }
  447. parentPaths.push(newParentPath);
  448. parentPath = newParentPath;
  449. } while (true);
  450. assert(newEntryParent != nullptr); // Must exist
  451. if(newHierarchyRoot != nullptr)
  452. *newHierarchyRoot = newEntryParent;
  453. while(!parentPaths.empty())
  454. {
  455. WPath curPath = parentPaths.top();
  456. parentPaths.pop();
  457. newEntryParent = addDirectoryInternal(newEntryParent, curPath);
  458. }
  459. if(newHierarchyLeaf != nullptr)
  460. *newHierarchyLeaf = newEntryParent;
  461. }
  462. WPath ProjectLibrary::getMetaPath(const CM::WPath& path) const
  463. {
  464. WPath metaPath = toPath(toWString(path) + L".meta");
  465. return metaPath;
  466. }
  467. bool ProjectLibrary::isMeta(const WPath& fullPath) const
  468. {
  469. return PathUtil::getExtension(fullPath) == toPath(L".meta");
  470. }
  471. void ProjectLibrary::onMonitorFileModified(const WString& path)
  472. {
  473. if(!isMeta(toPath(path)))
  474. checkForModifications(path);
  475. else
  476. {
  477. WPath resourcePath = toPath(path);
  478. PathUtil::replaceExtension(resourcePath, L"");
  479. checkForModifications(toWString(resourcePath));
  480. }
  481. }
  482. }