Home Reference Source

cables_dev/cables_electron/src/utils/files_util.js

  1. import { SharedFilesUtil, utilProvider } from "cables-shared-api";
  2. import fs from "fs";
  3. import path from "path";
  4. import chokidar from "chokidar";
  5. import helper from "./helper_util.js";
  6. import cables from "../cables.js";
  7. import opsUtil from "./ops_util.js";
  8. import electronApp from "../electron/main.js";
  9. import projectsUtil from "./projects_util.js";
  10. import settings from "../electron/electron_settings.js";
  11.  
  12. class FilesUtil extends SharedFilesUtil
  13. {
  14. constructor(provider)
  15. {
  16. super(provider);
  17.  
  18. const watcherOptions = {
  19. "ignored": /(^|[\/\\])\../,
  20. "ignorePermissionErrors": true,
  21. "ignoreInitial": true,
  22. "persistent": true,
  23. "followSymlinks": true,
  24. "disableGlobbing": true,
  25. "awaitWriteFinish": {
  26. "stabilityThreshold": 200
  27. }
  28. };
  29.  
  30. this._opChangeWatcher = chokidar.watch([], watcherOptions);
  31. this._opChangeWatcher.on("change", (fileName) =>
  32. {
  33. const opName = opsUtil.getOpNameByAbsoluteFileName(fileName);
  34. if (opName)
  35. {
  36. const opId = opsUtil.getOpIdByObjName(opName);
  37. const code = opsUtil.getOpCode(opName);
  38. electronApp.sendTalkerMessage("executeOp", { "name": opName, "forceReload": true, "id": opId, "code": code });
  39. }
  40. });
  41.  
  42. this._opChangeWatcher.on("unlink", (fileName) =>
  43. {
  44. const opName = opsUtil.getOpNameByAbsoluteFileName(fileName);
  45. if (opName)
  46. {
  47. electronApp.sendTalkerMessage("deleteOp", { "name": opName });
  48. }
  49. });
  50.  
  51. this._assetChangeWatcher = chokidar.watch([], watcherOptions);
  52. this._assetChangeWatcher.on("change", (fileName) =>
  53. {
  54. electronApp.sendTalkerMessage("fileUpdated", { "filename": helper.pathToFileURL(fileName) });
  55. });
  56.  
  57. this._assetChangeWatcher.on("unlink", (fileName) =>
  58. {
  59. electronApp.sendTalkerMessage("fileDeleted", { "fileName": helper.pathToFileURL(fileName) });
  60. });
  61. }
  62.  
  63. registerAssetChangeListeners(project, removeOthers = false)
  64. {
  65. if (!project || !project.ops) return;
  66. if (removeOthers) this._assetChangeWatcher.removeAllListeners();
  67. const fileNames = projectsUtil.getUsedAssetFilenames(project, true);
  68. this._assetChangeWatcher.add(fileNames);
  69. }
  70.  
  71. registerOpChangeListeners(opNames, removeOthers = false)
  72. {
  73. if (!opNames) return;
  74. if (removeOthers) this._opChangeWatcher.removeAllListeners();
  75. const fileNames = [];
  76. opNames.forEach((opName) =>
  77. {
  78. if (opsUtil.isOpNameValid(opName))
  79. {
  80. const opFile = opsUtil.getOpAbsoluteFileName(opName);
  81. if (opFile)
  82. {
  83. fileNames.push(opFile);
  84. }
  85. }
  86. });
  87. this._opChangeWatcher.add(fileNames);
  88. }
  89.  
  90. unregisterOpChangeListeners(opNames)
  91. {
  92. if (!opNames) return;
  93. const fileNames = [];
  94. opNames.forEach((opName) =>
  95. {
  96. if (opsUtil.isOpNameValid(opName))
  97. {
  98. const opFile = opsUtil.getOpAbsoluteFileName(opName);
  99. if (opFile)
  100. {
  101. fileNames.push(opFile);
  102. }
  103. }
  104. });
  105. this._opChangeWatcher.unwatch(fileNames);
  106. }
  107.  
  108. async unregisterChangeListeners()
  109. {
  110. await this._assetChangeWatcher.close();
  111. await this._opChangeWatcher.close();
  112. }
  113.  
  114. getFileDb(filePath, user, project, cachebuster = "")
  115. {
  116. const stats = fs.statSync(filePath);
  117. const fileName = path.basename(filePath);
  118. const suffix = path.extname(fileName);
  119. return {
  120. "_id": helper.generateRandomId(),
  121. "name": fileName,
  122. "type": this.getFileType(fileName),
  123. "suffix": suffix,
  124. "fileName": fileName,
  125. "projectId": project._id,
  126. "userId": user._id,
  127. "updated": stats.mtime,
  128. "created": stats.ctime,
  129. "cachebuster": cachebuster,
  130. "isLibraryFile": filePath.includes(cables.getAssetLibraryPath()),
  131. "__v": 0,
  132. "size": stats.size,
  133. "path": filePath
  134. };
  135. }
  136.  
  137. getFileAssetLocation(fileDb)
  138. {
  139. return fileDb.path;
  140. }
  141.  
  142. getFileAssetUrlPath(fileDb)
  143. {
  144. if (!fileDb) return "";
  145. let assetDir = "";
  146. let assetFilePath = fileDb.path;
  147. if (fileDb.isLibraryFile)
  148. {
  149. assetDir = cables.getAssetLibraryPath();
  150. assetFilePath = path.join(assetDir, this.getAssetFileName(fileDb));
  151. }
  152. else if (!assetFilePath)
  153. {
  154. assetFilePath = path.join(assetDir, this.getAssetFileName(fileDb));
  155. }
  156. return helper.pathToFileURL(assetFilePath);
  157. }
  158.  
  159. getLibraryFiles()
  160. {
  161. const p = cables.getAssetLibraryPath();
  162. return this.readAssetDir(0, p, p);
  163. }
  164.  
  165. getFileIconName(fileDb)
  166. {
  167. let icon = "file";
  168.  
  169. if (fileDb.type === "SVG") icon = "pen-tool";
  170. else if (fileDb.type === "image") icon = "image";
  171. else if (fileDb.type === "gltf" || fileDb.type === "3d json") icon = "cube";
  172. else if (fileDb.type === "video") icon = "film";
  173. else if (fileDb.type === "font") icon = "type";
  174. else if (fileDb.type === "JSON") icon = "code";
  175. else if (fileDb.type === "audio") icon = "headphones";
  176.  
  177. return icon;
  178. }
  179.  
  180. readAssetDir(lvl, filePath, origPath, urlPrefix = "")
  181. {
  182. const arr = [];
  183. if (!fs.existsSync(filePath))
  184. {
  185. this._log.error("could not find library assets at", filePath, "check your cables_env_local.json");
  186. return arr;
  187. }
  188. const files = fs.readdirSync(filePath);
  189. for (const i in files)
  190. {
  191. const fullPath = path.join(filePath, "/", files[i]);
  192. const urlPath = helper.pathToFileURL(fullPath);
  193.  
  194. if (files[i] && !files[i].startsWith("."))
  195. {
  196. const s = fs.statSync(fullPath);
  197. if (s.isDirectory() && fs.readdirSync(fullPath).length > 0)
  198. {
  199. arr.push({
  200. "d": true,
  201. "n": files[i],
  202. "t": "dir",
  203. "l": lvl,
  204. "c": this.readAssetDir(lvl + 1, path.join(fullPath, "/"), origPath, urlPrefix),
  205. "p": urlPath,
  206. "isLibraryFile": true
  207. });
  208. }
  209. else if (files[i].toLowerCase().endsWith(".fileinfo.json")) continue;
  210. else
  211. {
  212. let type = "unknown";
  213. if (files[i].endsWith("jpg") || files[i].endsWith("png") || files[i].endsWith("jpeg")) type = "image";
  214. else if (files[i].endsWith("mp3") || files[i].endsWith("ogg") || files[i].endsWith("wav")) type = "audio";
  215. else if (files[i].endsWith("3d.json")) type = "3d json";
  216. else if (files[i].endsWith("json")) type = "json";
  217. else if (files[i].endsWith("mp4")) type = "video";
  218.  
  219. const fileData = {
  220. "d": false,
  221. "n": files[i],
  222. "t": type,
  223. "l": lvl,
  224. "p": urlPath,
  225. "type": type,
  226. "updated": "bla",
  227. "isLibraryFile": true
  228. };
  229. fileData.icon = this.getFileIconName(fileData);
  230. let stats = fs.statSync(fullPath);
  231. if (stats && stats.mtime)
  232. {
  233. fileData.updated = new Date(stats.mtime).getTime();
  234. }
  235.  
  236. arr.push(fileData);
  237. }
  238. }
  239. }
  240. return arr;
  241. }
  242.  
  243. getPatchFiles()
  244. {
  245. const arr = [];
  246. const fileHierarchy = {};
  247.  
  248. const project = settings.getCurrentProject();
  249. if (!project) return arr;
  250.  
  251. const projectDir = settings.getCurrentProjectDir();
  252. const fileNames = projectsUtil.getUsedAssetFilenames(project);
  253. fileNames.forEach((fileName) =>
  254. {
  255. let type = "unknown";
  256. if (fileName.endsWith("jpg") || fileName.endsWith("png") || fileName.endsWith("jpeg")) type = "image";
  257. else if (fileName.endsWith("mp3") || fileName.endsWith("ogg") || fileName.endsWith("wav")) type = "audio";
  258. else if (fileName.endsWith("3d.json")) type = "3d json";
  259. else if (fileName.endsWith("json")) type = "json";
  260. else if (fileName.endsWith("mp4")) type = "video";
  261.  
  262. let dirName = path.join(path.dirname(fileName), "/");
  263. dirName = dirName.replaceAll("\\", "/");
  264.  
  265. if (!fileHierarchy.hasOwnProperty(dirName)) fileHierarchy[dirName] = [];
  266. const fileUrl = helper.pathToFileURL(fileName);
  267.  
  268. const fileData = {
  269. "d": false,
  270. "n": path.basename(fileUrl),
  271. "t": type,
  272. "l": 0,
  273. "p": fileUrl,
  274. "type": type,
  275. "updated": "bla",
  276. "isReference": true
  277. };
  278. fileData.icon = this.getFileIconName(fileData);
  279.  
  280. let stats = fs.statSync(fileName);
  281. if (stats && stats.mtime)
  282. {
  283. fileData.updated = new Date(stats.mtime).getTime();
  284. }
  285. fileHierarchy[dirName].push(fileData);
  286. });
  287. const dirNames = Object.keys(fileHierarchy);
  288. for (let dirName of dirNames)
  289. {
  290. let displayName = path.join(dirName, "/");
  291.  
  292. arr.push({
  293. "d": true,
  294. "n": displayName,
  295. "t": "dir",
  296. "l": 1,
  297. "c": fileHierarchy[dirName],
  298. "p": dirName
  299. });
  300. }
  301. return arr;
  302. }
  303.  
  304. isAssetLibraryLocation(filePath)
  305. {
  306. if (!filePath) return false;
  307. return filePath.toLowerCase().includes(cables.getAssetLibraryPath());
  308. }
  309. }
  310.  
  311. export default new FilesUtil(utilProvider);