Home Reference Source

cables_dev/cables_electron/src/electron/electron_settings.js

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