BsProjectLibrary.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  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 = Importer::instance().import(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(), metaPath);
  294. }
  295. WString internalResourcesPath = Path::combine(EditorApplication::instance().getActiveProjectPath(), INTERNAL_RESOURCES_DIR);
  296. if(!FileSystem::isDirectory(internalResourcesPath))
  297. FileSystem::createDir(internalResourcesPath);
  298. internalResourcesPath = Path::combine(internalResourcesPath, toWString(importedResource.getUUID()) + L".asset");
  299. gResources().save(importedResource, internalResourcesPath, true);
  300. ResourceManifestPtr manifest = gResources().getResourceManifest();
  301. manifest->registerResource(importedResource.getUUID(), 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::WString& fullPath) const
  319. {
  320. Vector<WString>::type pathElems = Path::split(fullPath);
  321. Vector<WString>::type rootElems = Path::split(mRootEntry->path);
  322. auto pathIter = pathElems.begin();
  323. auto rootIter = rootElems.begin();
  324. while(pathIter != pathElems.end() && rootIter != rootElems.end() && Path::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(Path::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::WString& oldPath, const CM::WString& newPath)
  356. {
  357. if(FileSystem::isFile(oldPath) || FileSystem::isDirectory(oldPath))
  358. FileSystem::move(oldPath, newPath);
  359. WString oldMetaPath = getMetaPath(oldPath);
  360. WString 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(!Path::includes(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. WString parentPath = Path::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 = Path::getFilename(newPath);
  399. if(oldEntry->type == LibraryEntryType::Directory) // Update child paths
  400. {
  401. Stack<LibraryEntry*>::type todo;
  402. todo.push(oldEntry);
  403. while(!todo.empty())
  404. {
  405. LibraryEntry* curEntry = todo.top();
  406. todo.pop();
  407. DirectoryEntry* curDirEntry = static_cast<DirectoryEntry*>(curEntry);
  408. for(auto& child : curDirEntry->mChildren)
  409. {
  410. child->path = Path::combine(child->parent->path, child->elementName);
  411. if(child->type == LibraryEntryType::Directory)
  412. todo.push(child);
  413. }
  414. }
  415. }
  416. if(!onEntryRemoved.empty())
  417. onEntryRemoved(oldPath);
  418. if(!onEntryAdded.empty())
  419. onEntryAdded(newPath);
  420. if(newHierarchyParent != nullptr)
  421. checkForModifications(newHierarchyParent->path);
  422. }
  423. }
  424. else // Moving from outside of the Resources folder (likely adding a new resource)
  425. {
  426. checkForModifications(newPath);
  427. }
  428. }
  429. void ProjectLibrary::deleteEntry(const CM::WString& path)
  430. {
  431. if(FileSystem::isFile(path))
  432. FileSystem::remove(path);
  433. LibraryEntry* entry = findEntry(path);
  434. if(entry != nullptr)
  435. {
  436. if(entry->type == LibraryEntryType::File)
  437. {
  438. deleteResourceInternal(static_cast<ResourceEntry*>(entry));
  439. WString metaPath = getMetaPath(path);
  440. if(FileSystem::isFile(metaPath))
  441. FileSystem::remove(metaPath);
  442. }
  443. else if(entry->type == LibraryEntryType::Directory)
  444. deleteDirectoryInternal(static_cast<DirectoryEntry*>(entry));
  445. }
  446. }
  447. void ProjectLibrary::createInternalParentHierarchy(const CM::WString& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
  448. {
  449. WString parentPath = fullPath;
  450. DirectoryEntry* newEntryParent = nullptr;
  451. Stack<WString>::type parentPaths;
  452. do
  453. {
  454. WString newParentPath = Path::parentPath(parentPath);
  455. if(newParentPath == parentPath)
  456. break;
  457. LibraryEntry* newEntryParentLib = findEntry(newParentPath);
  458. if(newEntryParentLib != nullptr)
  459. {
  460. assert(newEntryParentLib->type == LibraryEntryType::Directory);
  461. newEntryParent = static_cast<DirectoryEntry*>(newEntryParentLib);
  462. break;
  463. }
  464. parentPaths.push(newParentPath);
  465. parentPath = newParentPath;
  466. } while (true);
  467. assert(newEntryParent != nullptr); // Must exist
  468. if(newHierarchyRoot != nullptr)
  469. *newHierarchyRoot = newEntryParent;
  470. while(!parentPaths.empty())
  471. {
  472. WString curPath = parentPaths.top();
  473. parentPaths.pop();
  474. newEntryParent = addDirectoryInternal(newEntryParent, curPath);
  475. }
  476. if(newHierarchyLeaf != nullptr)
  477. *newHierarchyLeaf = newEntryParent;
  478. }
  479. WString ProjectLibrary::getMetaPath(const CM::WString& path) const
  480. {
  481. WString metaPath = path + L".meta";
  482. return metaPath;
  483. }
  484. bool ProjectLibrary::isMeta(const WString& fullPath) const
  485. {
  486. return Path::getExtension(fullPath) == L".meta";
  487. }
  488. void ProjectLibrary::onMonitorFileModified(const WString& path)
  489. {
  490. if(!isMeta(path))
  491. checkForModifications(path);
  492. else
  493. {
  494. WString resourcePath = path;
  495. Path::replaceExtension(resourcePath, L"");
  496. checkForModifications(resourcePath);
  497. }
  498. }
  499. }