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