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