Home Reference Source

cables_dev/cables_electron/src/electron/electron_settings.js

  1. import path from "path";
  2. import fs from "fs";
  3. import mkdirp from "mkdirp";
  4. import { app } from "electron";
  5. import jsonfile from "jsonfile";
  6. import helper from "../utils/helper_util.js";
  7. import logger from "../utils/logger.js";
  8. import projectsUtil from "../utils/projects_util.js";
  9. import cables from "../cables.js";
  10. import electronApp from "./main.js";
  11.  
  12. class ElectronSettings
  13. {
  14. constructor(storageDir)
  15. {
  16. this._log = logger;
  17. this.SESSION_PARTITION = "persist:cables:standalone";
  18.  
  19. if (storageDir && !fs.existsSync(storageDir))
  20. {
  21. mkdirp.sync(storageDir);
  22. }
  23. this.MAIN_CONFIG_NAME = "cables-electron-preferences";
  24. this.PATCHID_FIELD = "patchId";
  25. this.PROJECTFILE_FIELD = "patchFile";
  26. this.CURRENTPROJECTDIR_FIELD = "currentPatchDir";
  27. this.STORAGEDIR_FIELD = "storageDir";
  28. this.USER_SETTINGS_FIELD = "userSettings";
  29. this.RECENT_PROJECTS_FIELD = "recentProjects";
  30. this.OPEN_DEV_TOOLS_FIELD = "openDevTools";
  31. this.WINDOW_ZOOM_FACTOR = "windowZoomFactor";
  32. this.WINDOW_BOUNDS = "windowBounds";
  33. this.DOWNLOAD_PATH = "downloadPath";
  34.  
  35. this.opts = {};
  36. this.opts.defaults = {};
  37. this.opts.configName = this.MAIN_CONFIG_NAME;
  38. this.opts.defaults[this.USER_SETTINGS_FIELD] = {};
  39. this.opts.defaults[this.PATCHID_FIELD] = null;
  40. this.opts.defaults[this.PROJECTFILE_FIELD] = null;
  41. this.opts.defaults[this.CURRENTPROJECTDIR_FIELD] = null;
  42. this.opts.defaults[this.STORAGEDIR_FIELD] = storageDir;
  43. this.opts.defaults[this.RECENT_PROJECTS_FIELD] = {};
  44. this.opts.defaults[this.OPEN_DEV_TOOLS_FIELD] = false;
  45. this.opts.defaults[this.DOWNLOAD_PATH] = app.getPath("downloads");
  46.  
  47. this.data = this.opts.defaults;
  48. mkdirp(this.data[this.STORAGEDIR_FIELD]);
  49. this.refresh();
  50. this.set("currentUser", this.getCurrentUser(), true);
  51. }
  52.  
  53. refresh()
  54. {
  55. if (this.data && this.data.hasOwnProperty(this.STORAGEDIR_FIELD) && this.data[this.STORAGEDIR_FIELD])
  56. {
  57. const userDataPath = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + ".json");
  58. const storedData = this._parseDataFile(userDataPath, this.opts.defaults);
  59. Object.keys(this.opts.defaults).forEach((key) =>
  60. {
  61. if (!storedData.hasOwnProperty(key)) storedData[key] = this.opts.defaults[key];
  62. });
  63. this.data = storedData;
  64. this.data.paths = {
  65. "home": app.getPath("home"),
  66. "appData": app.getPath("appData"),
  67. "userData": app.getPath("userData"),
  68. "sessionData": app.getPath("sessionData"),
  69. "temp": app.getPath("temp"),
  70. "exe": app.getPath("exe"),
  71. "module": app.getPath("module"),
  72. "desktop": app.getPath("desktop"),
  73. "documents": app.getPath("documents"),
  74. "downloads": app.getPath("downloads"),
  75. "music": app.getPath("music"),
  76. "pictures": app.getPath("pictures"),
  77. "videos": app.getPath("videos"),
  78. "logs": app.getPath("logs"),
  79. "crashDumps": app.getPath("crashDumps"),
  80. };
  81. const dir = this.get(this.CURRENTPROJECTDIR_FIELD);
  82. const id = this.get(this.PATCHID_FIELD);
  83. if (dir && id)
  84. {
  85. this.data.paths.assetPath = path.join(dir, "assets", id, "/");
  86. }
  87. else if (id)
  88. {
  89. this.data.paths.assetPath = path.join(".", "assets", id, "/");
  90. }
  91. if (process.platform === "win32")
  92. {
  93. this.data.paths.recent = app.getPath("recent");
  94. }
  95. }
  96. }
  97.  
  98. get(key)
  99. {
  100. if (!this.data)
  101. {
  102. return null;
  103. }
  104. return this.data[key];
  105. }
  106.  
  107. set(key, val, silent)
  108. {
  109. this.data[key] = val;
  110. let configFileName = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + ".json");
  111. if (!silent)
  112. {
  113. fs.writeFileSync(configFileName, JSON.stringify(this.data));
  114. this.refresh();
  115. }
  116. }
  117.  
  118. getCurrentProjectDir()
  119. {
  120. let value = this.get(this.CURRENTPROJECTDIR_FIELD);
  121. if (value && !value.endsWith("/")) value = path.join(value, "/");
  122. return value;
  123. }
  124.  
  125. getCurrentProject()
  126. {
  127. return this._currentProject;
  128. }
  129.  
  130.  
  131. setProject(projectFile, newProject)
  132. {
  133. let projectDir = null;
  134. if (projectFile) projectDir = path.dirname(projectFile);
  135. this._setCurrentProjectFile(projectFile);
  136. this._setCurrentProjectDir(projectDir);
  137. this._setCurrentProject(projectFile, newProject);
  138. this.addToRecentProjects(projectFile, newProject);
  139. }
  140.  
  141. getCurrentUser()
  142. {
  143. let username = this.getUserSetting("authorName", "") || "";
  144. return {
  145. "username": username,
  146. "_id": helper.generateRandomId(),
  147. "profile_theme": "dark",
  148. "isStaff": false,
  149. "usernameLowercase": username.toLowerCase(),
  150. "isAdmin": false,
  151. "theme": "dark",
  152. "created": Date.now()
  153. };
  154. }
  155.  
  156. setUserSettings(value)
  157. {
  158. this.set(this.USER_SETTINGS_FIELD, value);
  159. }
  160.  
  161. getUserSetting(key, defaultValue = null)
  162. {
  163. const userSettings = this.get(this.USER_SETTINGS_FIELD);
  164. if (!userSettings) return defaultValue;
  165. if (!userSettings.hasOwnProperty(key)) return defaultValue;
  166. return userSettings[key];
  167. }
  168.  
  169. getCurrentProjectFile()
  170. {
  171. const projectFile = this.get(this.PROJECTFILE_FIELD);
  172. if (projectFile && projectFile.endsWith(projectsUtil.CABLES_PROJECT_FILE_EXTENSION)) return projectFile;
  173. return null;
  174. }
  175.  
  176. getBuildInfo()
  177. {
  178. const coreFile = path.join(cables.getUiDistPath(), "js", "buildinfo.json");
  179. const uiFile = path.join(cables.getUiDistPath(), "buildinfo.json");
  180. const standaloneFile = path.join(cables.getStandaloneDistPath(), "public", "js", "buildinfo.json");
  181. let core = {};
  182. if (fs.existsSync(coreFile))
  183. {
  184. try
  185. {
  186. core = jsonfile.readFileSync(coreFile);
  187. }
  188. catch (e)
  189. {
  190. this._log.info("failed to parse buildinfo from", coreFile);
  191. }
  192. }
  193.  
  194. let ui = {};
  195. if (fs.existsSync(uiFile))
  196. {
  197. try
  198. {
  199. ui = jsonfile.readFileSync(uiFile);
  200. }
  201. catch (e)
  202. {
  203. this._log.info("failed to parse buildinfo from", uiFile);
  204. }
  205. }
  206.  
  207. let api = {};
  208. if (fs.existsSync(standaloneFile))
  209. {
  210. try
  211. {
  212. api = jsonfile.readFileSync(standaloneFile);
  213. }
  214. catch (e)
  215. {
  216. this._log.info("failed to parse buildinfo from", standaloneFile);
  217. }
  218. }
  219.  
  220.  
  221. return {
  222. "updateWarning": false,
  223. "core": core,
  224. "ui": ui,
  225. "api": api
  226. };
  227. }
  228.  
  229. // helper methods
  230. _parseDataFile(filePath, defaults)
  231. {
  232. try
  233. {
  234. let jsonContent = fs.readFileSync(filePath);
  235. return JSON.parse(jsonContent);
  236. }
  237. catch (error)
  238. {
  239. return defaults;
  240. }
  241. }
  242.  
  243. getRecentProjects()
  244. {
  245. const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
  246. return Object.values(recentProjects);
  247. }
  248.  
  249. getRecentProjectFile(projectId)
  250. {
  251. const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
  252. for (const file in recentProjects)
  253. {
  254. const recent = recentProjects[file];
  255. if (recent && (recent._id === projectId || recent.shortId === projectId))
  256. {
  257. if (fs.existsSync(file)) return file;
  258. }
  259. }
  260. return null;
  261. }
  262.  
  263. setRecentProjects(recents)
  264. {
  265. if (!recents) recents = {};
  266. return this.set(this.RECENT_PROJECTS_FIELD, recents);
  267. }
  268.  
  269. replaceInRecentProjects(oldFile, newFile, newProject)
  270. {
  271. const recents = this.get(this.RECENT_PROJECTS_FIELD) || {};
  272. recents[newFile] = this._toRecentProjectInfo(newProject);
  273. delete recents[oldFile];
  274. this._updateRecentProjects();
  275. return this.getRecentProjects();
  276. }
  277.  
  278. _updateRecentProjects()
  279. {
  280. const recents = this.get(this.RECENT_PROJECTS_FIELD) || {};
  281.  
  282.  
  283. let files = Object.keys(recents);
  284. files = files.filter((f) => { return fs.existsSync(f); });
  285. files = files.sort((f1, f2) =>
  286. {
  287. const p1 = recents[f1];
  288. const p2 = recents[f2];
  289. if (!p1 || !p1.updated) return 1;
  290. if (!p2 || !p2.updated) return -1;
  291. return p2.updated - p1.updated;
  292. });
  293. files = helper.uniqueArray(files);
  294. const newRecents = {};
  295. for (let i = 0; i < 10; i++)
  296. {
  297. if (i > files.length) break;
  298. const key = files[i];
  299. if (key)
  300. {
  301. try
  302. {
  303. const project = jsonfile.readFileSync(key);
  304. newRecents[key] = this._toRecentProjectInfo(project);
  305. }
  306. catch (e)
  307. {
  308. this._log.info("failed to parse project file for recent projects, ignoring", key);
  309. }
  310. }
  311. }
  312. this.setRecentProjects(newRecents);
  313. }
  314.  
  315. _setCurrentProjectFile(value)
  316. {
  317. this.set(this.PROJECTFILE_FIELD, value);
  318. }
  319.  
  320. _toRecentProjectInfo(project)
  321. {
  322. if (!project) return null;
  323. return {
  324. "_id": project._id,
  325. "shortId": project.shortId,
  326. "name": project.name,
  327. "screenshot": project.screenshot,
  328. "created": project.created,
  329. "updated": project.updated
  330. };
  331. }
  332.  
  333. _setCurrentProjectDir(value)
  334. {
  335. if (value) value = path.join(value, "/");
  336. this.set(this.CURRENTPROJECTDIR_FIELD, value);
  337. }
  338.  
  339. _setCurrentProject(projectFile, project)
  340. {
  341. this._currentProject = project;
  342. projectsUtil.invalidateProjectCaches();
  343. if (project)
  344. {
  345. this.set(this.PATCHID_FIELD, project._id);
  346. }
  347. if (projectFile && project)
  348. {
  349. const projectName = path.basename(projectFile, "." + projectsUtil.CABLES_PROJECT_FILE_EXTENSION);
  350. if (project.name !== projectName)
  351. {
  352. project.name = projectName;
  353. project.summary = project.summary || {};
  354. project.summary.title = project.name;
  355. projectsUtil.writeProjectToFile(projectFile, project);
  356. }
  357. this._updateRecentProjects();
  358. }
  359.  
  360. electronApp.updateTitle();
  361. }
  362.  
  363. addToRecentProjects(projectFile, project)
  364. {
  365. if (!projectFile || !project) return;
  366. app.addRecentDocument(projectFile);
  367. const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
  368. const recent = this._toRecentProjectInfo(project);
  369. if (recent) recentProjects[projectFile] = recent;
  370. this.setRecentProjects(recentProjects);
  371. this._updateRecentProjects();
  372. }
  373.  
  374. getProjectFromFile(projectFile)
  375. {
  376. if (!projectFile || !fs.existsSync(projectFile)) return null;
  377. const project = fs.readFileSync(projectFile);
  378. try
  379. {
  380. return JSON.parse(project.toString("utf-8"));
  381. }
  382. catch (e)
  383. {
  384. this._log.error("failed to parse project from projectfile", projectFile, e);
  385. }
  386. return null;
  387. }
  388.  
  389. getDownloadPath()
  390. {
  391. const customDownloadPath = this.get(this.DOWNLOAD_PATH);
  392. return customDownloadPath || app.getPath("downloads");
  393. }
  394. }
  395. export default new ElectronSettings(path.join(app.getPath("userData")));
  396.