BsProjectLibrary.cpp 16 KB

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