BsProjectLibrary.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  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::WString& 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::WString& path, const CM::WString& name, DirectoryEntry* parent)
  25. :LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0)
  26. { }
  27. ProjectLibrary::DirectoryEntry::DirectoryEntry(const CM::WString& 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(!Path::includes(fullPath, EditorApplication::instance().getResourcesFolderPath()))
  58. return; // Folder not part of our resources path, so no modifications
  59. if(mRootEntry == nullptr)
  60. {
  61. WString resPath = EditorApplication::instance().getResourcesFolderPath();
  62. mRootEntry = cm_new<DirectoryEntry>(resPath, Path::getFilename(resPath), nullptr);
  63. }
  64. WString pathToSearch = fullPath;
  65. LibraryEntry* entry = findEntry(pathToSearch);
  66. if(entry == nullptr) // File could be new, try to find parent directory entry
  67. {
  68. WString parentDirPath = Path::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(pathToSearch);
  86. }
  87. if(newHierarchyParent != nullptr)
  88. checkForModifications(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<WString>::type childFiles;
  112. Vector<WString>::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. WString sourceFilePath = filePath;
  131. Path::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 && Path::equals(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 && Path::equals(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::WString& filePath)
  204. {
  205. ResourceEntry* newResource = cm_new<ResourceEntry>(filePath, Path::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::WString& dirPath)
  213. {
  214. DirectoryEntry* newEntry = cm_new<DirectoryEntry>(dirPath, Path::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. CM::Vector<LibraryEntry*>::type childrenToDestroy = directory->mChildren;
  246. for(auto& child : childrenToDestroy)
  247. {
  248. if(child->type == LibraryEntryType::Directory)
  249. deleteDirectoryInternal(static_cast<DirectoryEntry*>(child));
  250. else
  251. deleteResourceInternal(static_cast<ResourceEntry*>(child));
  252. }
  253. DirectoryEntry* parent = directory->parent;
  254. auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(),
  255. [&] (const LibraryEntry* entry) { return entry == directory; });
  256. parent->mChildren.erase(findIter);
  257. if(!onEntryRemoved.empty())
  258. onEntryRemoved(directory->path);
  259. cm_delete(directory);
  260. }
  261. void ProjectLibrary::reimportResourceInternal(ResourceEntry* resource)
  262. {
  263. WString ext = Path::getExtension(resource->path);
  264. WString metaPath = resource->path + 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(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(resource->path);
  288. HResource importedResource;
  289. if(resource->meta == nullptr)
  290. {
  291. importedResource = Importer::instance().import(resource->path, importOptions);
  292. resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions);
  293. FileSerializer fs;
  294. fs.encode(resource->meta.get(), metaPath);
  295. }
  296. else
  297. {
  298. importedResource = HResource(resource->meta->getUUID());
  299. Importer::instance().reimport(importedResource, resource->path, importOptions);
  300. }
  301. WString internalResourcesPath = Path::combine(EditorApplication::instance().getActiveProjectPath(), INTERNAL_RESOURCES_DIR);
  302. if(!FileSystem::isDirectory(internalResourcesPath))
  303. FileSystem::createDir(internalResourcesPath);
  304. internalResourcesPath = Path::combine(internalResourcesPath, toWString(importedResource.getUUID()) + L".asset");
  305. gResources().save(importedResource, internalResourcesPath, true);
  306. gResources().unload(importedResource);
  307. ResourceManifestPtr manifest = gResources().getResourceManifest();
  308. manifest->registerResource(importedResource.getUUID(), internalResourcesPath);
  309. resource->lastUpdateTime = std::time(nullptr);
  310. }
  311. }
  312. bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const
  313. {
  314. if(resource->meta == nullptr)
  315. return false;
  316. ResourceManifestPtr manifest = gResources().getResourceManifest();
  317. if(!manifest->uuidExists(resource->meta->getUUID()))
  318. return false;
  319. const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
  320. if(!FileSystem::isFile(path))
  321. return false;
  322. std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path);
  323. return lastModifiedTime <= resource->lastUpdateTime;
  324. }
  325. ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const CM::WString& fullPath) const
  326. {
  327. Vector<WString>::type pathElems = Path::split(fullPath);
  328. Vector<WString>::type rootElems = Path::split(mRootEntry->path);
  329. auto pathIter = pathElems.begin();
  330. auto rootIter = rootElems.begin();
  331. while(pathIter != pathElems.end() && rootIter != rootElems.end() && Path::comparePathElements(*pathIter, *rootIter))
  332. {
  333. ++pathIter;
  334. ++rootIter;
  335. }
  336. if(pathIter == pathElems.begin()) // Not a single entry matches Resources path
  337. return nullptr;
  338. --pathIter;
  339. Stack<LibraryEntry*>::type todo;
  340. todo.push(mRootEntry);
  341. while(!todo.empty())
  342. {
  343. LibraryEntry* current = todo.top();
  344. todo.pop();
  345. if(Path::comparePathElements(*pathIter, current->elementName))
  346. {
  347. ++pathIter;
  348. if(pathIter == pathElems.end())
  349. return current;
  350. while(!todo.empty())
  351. todo.pop();
  352. if(current->type == LibraryEntryType::Directory)
  353. {
  354. DirectoryEntry* dirEntry = static_cast<DirectoryEntry*>(current);
  355. for(auto& child : dirEntry->mChildren)
  356. todo.push(child);
  357. }
  358. }
  359. }
  360. return nullptr;
  361. }
  362. void ProjectLibrary::moveEntry(const CM::WString& oldPath, const CM::WString& newPath)
  363. {
  364. if(FileSystem::isFile(oldPath) || FileSystem::isDirectory(oldPath))
  365. FileSystem::move(oldPath, newPath);
  366. WString oldMetaPath = getMetaPath(oldPath);
  367. WString newMetaPath = getMetaPath(newPath);
  368. LibraryEntry* oldEntry = findEntry(oldPath);
  369. if(oldEntry != nullptr) // Moving from the Resources folder
  370. {
  371. // Moved outside of Resources, delete entry & meta file
  372. if(!Path::includes(newPath, EditorApplication::instance().getResourcesFolderPath()))
  373. {
  374. if(oldEntry->type == LibraryEntryType::File)
  375. {
  376. deleteResourceInternal(static_cast<ResourceEntry*>(oldEntry));
  377. if(FileSystem::isFile(oldMetaPath))
  378. FileSystem::remove(oldMetaPath);
  379. }
  380. else if(oldEntry->type == LibraryEntryType::Directory)
  381. deleteDirectoryInternal(static_cast<DirectoryEntry*>(oldEntry));
  382. }
  383. else // Just moving internally
  384. {
  385. if(FileSystem::isFile(oldMetaPath))
  386. FileSystem::move(oldMetaPath, newMetaPath);
  387. DirectoryEntry* parent = oldEntry->parent;
  388. auto findIter = std::find(parent->mChildren.begin(), parent->mChildren.end(), oldEntry);
  389. if(findIter != parent->mChildren.end())
  390. parent->mChildren.erase(findIter);
  391. WString parentPath = Path::parentPath(newPath);
  392. DirectoryEntry* newEntryParent = nullptr;
  393. LibraryEntry* newEntryParentLib = findEntry(parentPath);
  394. if(newEntryParentLib != nullptr)
  395. {
  396. assert(newEntryParentLib->type == LibraryEntryType::Directory);
  397. newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
  398. }
  399. DirectoryEntry* newHierarchyParent = nullptr;
  400. if(newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy
  401. createInternalParentHierarchy(newPath, &newHierarchyParent, &newEntryParent);
  402. newEntryParent->mChildren.push_back(oldEntry);
  403. oldEntry->parent = newEntryParent;
  404. oldEntry->path = newPath;
  405. oldEntry->elementName = Path::getFilename(newPath);
  406. if(oldEntry->type == LibraryEntryType::Directory) // Update child paths
  407. {
  408. Stack<LibraryEntry*>::type todo;
  409. todo.push(oldEntry);
  410. while(!todo.empty())
  411. {
  412. LibraryEntry* curEntry = todo.top();
  413. todo.pop();
  414. DirectoryEntry* curDirEntry = static_cast<DirectoryEntry*>(curEntry);
  415. for(auto& child : curDirEntry->mChildren)
  416. {
  417. child->path = Path::combine(child->parent->path, child->elementName);
  418. if(child->type == LibraryEntryType::Directory)
  419. todo.push(child);
  420. }
  421. }
  422. }
  423. if(!onEntryRemoved.empty())
  424. onEntryRemoved(oldPath);
  425. if(!onEntryAdded.empty())
  426. onEntryAdded(newPath);
  427. if(newHierarchyParent != nullptr)
  428. checkForModifications(newHierarchyParent->path);
  429. }
  430. }
  431. else // Moving from outside of the Resources folder (likely adding a new resource)
  432. {
  433. checkForModifications(newPath);
  434. }
  435. }
  436. void ProjectLibrary::deleteEntry(const CM::WString& path)
  437. {
  438. if(FileSystem::isFile(path))
  439. FileSystem::remove(path);
  440. LibraryEntry* entry = findEntry(path);
  441. if(entry != nullptr)
  442. {
  443. if(entry->type == LibraryEntryType::File)
  444. {
  445. deleteResourceInternal(static_cast<ResourceEntry*>(entry));
  446. WString metaPath = getMetaPath(path);
  447. if(FileSystem::isFile(metaPath))
  448. FileSystem::remove(metaPath);
  449. }
  450. else if(entry->type == LibraryEntryType::Directory)
  451. deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
  452. }
  453. }
  454. void ProjectLibrary::createInternalParentHierarchy(const CM::WString& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
  455. {
  456. WString parentPath = fullPath;
  457. DirectoryEntry* newEntryParent = nullptr;
  458. Stack<WString>::type parentPaths;
  459. do
  460. {
  461. WString newParentPath = Path::parentPath(parentPath);
  462. if(newParentPath == parentPath)
  463. break;
  464. LibraryEntry* newEntryParentLib = findEntry(newParentPath);
  465. if(newEntryParentLib != nullptr)
  466. {
  467. assert(newEntryParentLib->type == LibraryEntryType::Directory);
  468. newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
  469. break;
  470. }
  471. parentPaths.push(newParentPath);
  472. parentPath = newParentPath;
  473. } while (true);
  474. assert(newEntryParent != nullptr); // Must exist
  475. if(newHierarchyRoot != nullptr)
  476. *newHierarchyRoot = newEntryParent;
  477. while(!parentPaths.empty())
  478. {
  479. WString curPath = parentPaths.top();
  480. parentPaths.pop();
  481. newEntryParent = addDirectoryInternal(newEntryParent, curPath);
  482. }
  483. if(newHierarchyLeaf != nullptr)
  484. *newHierarchyLeaf = newEntryParent;
  485. }
  486. WString ProjectLibrary::getMetaPath(const CM::WString& path) const
  487. {
  488. WString metaPath = path + L".meta";
  489. return metaPath;
  490. }
  491. bool ProjectLibrary::isMeta(const WString& fullPath) const
  492. {
  493. return Path::getExtension(fullPath) == L".meta";
  494. }
  495. void ProjectLibrary::onMonitorFileModified(const WString& path)
  496. {
  497. if(!isMeta(path))
  498. checkForModifications(path);
  499. else
  500. {
  501. WString resourcePath = path;
  502. Path::replaceExtension(resourcePath, L"");
  503. checkForModifications(resourcePath);
  504. }
  505. }
  506. }