Home Reference Source

cables_dev/cables_electron/src/electron/electron_settings.js

import path from "path";
import fs from "fs";
import mkdirp from "mkdirp";
import { app } from "electron";
import jsonfile from "jsonfile";
import helper from "../utils/helper_util.js";
import logger from "../utils/logger.js";
import projectsUtil from "../utils/projects_util.js";
import cables from "../cables.js";
import electronApp from "./main.js";

class ElectronSettings
{
    constructor(storageDir)
    {
        this._log = logger;
        this.SESSION_PARTITION = "persist:cables:standalone";

        if (storageDir && !fs.existsSync(storageDir))
        {
            mkdirp.sync(storageDir);
        }
        this.MAIN_CONFIG_NAME = "cables-electron-preferences";
        this.PATCHID_FIELD = "patchId";
        this.PROJECTFILE_FIELD = "patchFile";
        this.CURRENTPROJECTDIR_FIELD = "currentPatchDir";
        this.STORAGEDIR_FIELD = "storageDir";
        this.USER_SETTINGS_FIELD = "userSettings";
        this.RECENT_PROJECTS_FIELD = "recentProjects";
        this.OPEN_DEV_TOOLS_FIELD = "openDevTools";
        this.WINDOW_ZOOM_FACTOR = "windowZoomFactor";
        this.WINDOW_BOUNDS = "windowBounds";
        this.DOWNLOAD_PATH = "downloadPath";

        this.opts = {};
        this.opts.defaults = {};
        this.opts.configName = this.MAIN_CONFIG_NAME;
        this.opts.defaults[this.USER_SETTINGS_FIELD] = {};
        this.opts.defaults[this.PATCHID_FIELD] = null;
        this.opts.defaults[this.PROJECTFILE_FIELD] = null;
        this.opts.defaults[this.CURRENTPROJECTDIR_FIELD] = null;
        this.opts.defaults[this.STORAGEDIR_FIELD] = storageDir;
        this.opts.defaults[this.RECENT_PROJECTS_FIELD] = {};
        this.opts.defaults[this.OPEN_DEV_TOOLS_FIELD] = false;
        this.opts.defaults[this.DOWNLOAD_PATH] = app.getPath("downloads");

        this.data = this.opts.defaults;
        mkdirp(this.data[this.STORAGEDIR_FIELD]);
        this.refresh();
        this.set("currentUser", this.getCurrentUser(), true);
    }

    refresh()
    {
        if (this.data && this.data.hasOwnProperty(this.STORAGEDIR_FIELD) && this.data[this.STORAGEDIR_FIELD])
        {
            const userDataPath = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + ".json");
            const storedData = this._parseDataFile(userDataPath, this.opts.defaults);
            Object.keys(this.opts.defaults).forEach((key) =>
            {
                if (!storedData.hasOwnProperty(key)) storedData[key] = this.opts.defaults[key];
            });
            this.data = storedData;
            this.data.paths = {
                "home": app.getPath("home"),
                "appData": app.getPath("appData"),
                "userData": app.getPath("userData"),
                "sessionData": app.getPath("sessionData"),
                "temp": app.getPath("temp"),
                "exe": app.getPath("exe"),
                "module": app.getPath("module"),
                "desktop": app.getPath("desktop"),
                "documents": app.getPath("documents"),
                "downloads": app.getPath("downloads"),
                "music": app.getPath("music"),
                "pictures": app.getPath("pictures"),
                "videos": app.getPath("videos"),
                "logs": app.getPath("logs"),
                "crashDumps": app.getPath("crashDumps"),
            };
            const dir = this.get(this.CURRENTPROJECTDIR_FIELD);
            const id = this.get(this.PATCHID_FIELD);
            if (dir && id)
            {
                this.data.paths.assetPath = path.join(dir, "assets", id, "/");
            }
            else if (id)
            {
                this.data.paths.assetPath = path.join(".", "assets", id, "/");
            }
            if (process.platform === "win32")
            {
                this.data.paths.recent = app.getPath("recent");
            }
        }
    }

    get(key)
    {
        if (!this.data)
        {
            return null;
        }
        return this.data[key];
    }

    set(key, val, silent)
    {
        this.data[key] = val;
        let configFileName = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + ".json");
        if (!silent)
        {
            fs.writeFileSync(configFileName, JSON.stringify(this.data));
            this.refresh();
        }
    }

    getCurrentProjectDir()
    {
        let value = this.get(this.CURRENTPROJECTDIR_FIELD);
        if (value && !value.endsWith("/")) value = path.join(value, "/");
        return value;
    }

    getCurrentProject()
    {
        return this._currentProject;
    }


    setProject(projectFile, newProject)
    {
        let projectDir = null;
        if (projectFile) projectDir = path.dirname(projectFile);
        this._setCurrentProjectFile(projectFile);
        this._setCurrentProjectDir(projectDir);
        this._setCurrentProject(projectFile, newProject);
        this.addToRecentProjects(projectFile, newProject);
    }

    getCurrentUser()
    {
        let username = this.getUserSetting("authorName", "") || "";
        return {
            "username": username,
            "_id": helper.generateRandomId(),
            "profile_theme": "dark",
            "isStaff": false,
            "usernameLowercase": username.toLowerCase(),
            "isAdmin": false,
            "theme": "dark",
            "created": Date.now()
        };
    }

    setUserSettings(value)
    {
        this.set(this.USER_SETTINGS_FIELD, value);
    }

    getUserSetting(key, defaultValue = null)
    {
        const userSettings = this.get(this.USER_SETTINGS_FIELD);
        if (!userSettings) return defaultValue;
        if (!userSettings.hasOwnProperty(key)) return defaultValue;
        return userSettings[key];
    }

    getCurrentProjectFile()
    {
        const projectFile = this.get(this.PROJECTFILE_FIELD);
        if (projectFile && projectFile.endsWith(projectsUtil.CABLES_PROJECT_FILE_EXTENSION)) return projectFile;
        return null;
    }

    getBuildInfo()
    {
        const coreFile = path.join(cables.getUiDistPath(), "js", "buildinfo.json");
        const uiFile = path.join(cables.getUiDistPath(), "buildinfo.json");
        const standaloneFile = path.join(cables.getStandaloneDistPath(), "public", "js", "buildinfo.json");
        let core = {};
        if (fs.existsSync(coreFile))
        {
            try
            {
                core = jsonfile.readFileSync(coreFile);
            }
            catch (e)
            {
                this._log.info("failed to parse buildinfo from", coreFile);
            }
        }

        let ui = {};
        if (fs.existsSync(uiFile))
        {
            try
            {
                ui = jsonfile.readFileSync(uiFile);
            }
            catch (e)
            {
                this._log.info("failed to parse buildinfo from", uiFile);
            }
        }

        let api = {};
        if (fs.existsSync(standaloneFile))
        {
            try
            {
                api = jsonfile.readFileSync(standaloneFile);
            }
            catch (e)
            {
                this._log.info("failed to parse buildinfo from", standaloneFile);
            }
        }


        return {
            "updateWarning": false,
            "core": core,
            "ui": ui,
            "api": api
        };
    }

    // helper methods
    _parseDataFile(filePath, defaults)
    {
        try
        {
            let jsonContent = fs.readFileSync(filePath);
            return JSON.parse(jsonContent);
        }
        catch (error)
        {
            return defaults;
        }
    }

    getRecentProjects()
    {
        const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
        return Object.values(recentProjects);
    }

    getRecentProjectFile(projectId)
    {
        const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
        for (const file in recentProjects)
        {
            const recent = recentProjects[file];
            if (recent && (recent._id === projectId || recent.shortId === projectId))
            {
                if (fs.existsSync(file)) return file;
            }
        }
        return null;
    }

    setRecentProjects(recents)
    {
        if (!recents) recents = {};
        return this.set(this.RECENT_PROJECTS_FIELD, recents);
    }

    replaceInRecentProjects(oldFile, newFile, newProject)
    {
        const recents = this.get(this.RECENT_PROJECTS_FIELD) || {};
        recents[newFile] = this._toRecentProjectInfo(newProject);
        delete recents[oldFile];
        this._updateRecentProjects();
        return this.getRecentProjects();
    }

    _updateRecentProjects()
    {
        const recents = this.get(this.RECENT_PROJECTS_FIELD) || {};


        let files = Object.keys(recents);
        files = files.filter((f) => { return fs.existsSync(f); });
        files = files.sort((f1, f2) =>
        {
            const p1 = recents[f1];
            const p2 = recents[f2];
            if (!p1 || !p1.updated) return 1;
            if (!p2 || !p2.updated) return -1;
            return p2.updated - p1.updated;
        });
        files = helper.uniqueArray(files);
        const newRecents = {};
        for (let i = 0; i < 10; i++)
        {
            if (i > files.length) break;
            const key = files[i];
            if (key)
            {
                try
                {
                    const project = jsonfile.readFileSync(key);
                    newRecents[key] = this._toRecentProjectInfo(project);
                }
                catch (e)
                {
                    this._log.info("failed to parse project file for recent projects, ignoring", key);
                }
            }
        }
        this.setRecentProjects(newRecents);
    }

    _setCurrentProjectFile(value)
    {
        this.set(this.PROJECTFILE_FIELD, value);
    }

    _toRecentProjectInfo(project)
    {
        if (!project) return null;
        return {
            "_id": project._id,
            "shortId": project.shortId,
            "name": project.name,
            "screenshot": project.screenshot,
            "created": project.created,
            "updated": project.updated
        };
    }

    _setCurrentProjectDir(value)
    {
        if (value) value = path.join(value, "/");
        this.set(this.CURRENTPROJECTDIR_FIELD, value);
    }

    _setCurrentProject(projectFile, project)
    {
        this._currentProject = project;
        projectsUtil.invalidateProjectCaches();
        if (project)
        {
            this.set(this.PATCHID_FIELD, project._id);
        }
        if (projectFile && project)
        {
            const projectName = path.basename(projectFile, "." + projectsUtil.CABLES_PROJECT_FILE_EXTENSION);
            if (project.name !== projectName)
            {
                project.name = projectName;
                project.summary = project.summary || {};
                project.summary.title = project.name;
                projectsUtil.writeProjectToFile(projectFile, project);
            }
            this._updateRecentProjects();
        }

        electronApp.updateTitle();
    }

    addToRecentProjects(projectFile, project)
    {
        if (!projectFile || !project) return;
        app.addRecentDocument(projectFile);
        const recentProjects = this.get(this.RECENT_PROJECTS_FIELD) || {};
        const recent = this._toRecentProjectInfo(project);
        if (recent) recentProjects[projectFile] = recent;
        this.setRecentProjects(recentProjects);
        this._updateRecentProjects();
    }

    getProjectFromFile(projectFile)
    {
        if (!projectFile || !fs.existsSync(projectFile)) return null;
        const project = fs.readFileSync(projectFile);
        try
        {
            return JSON.parse(project.toString("utf-8"));
        }
        catch (e)
        {
            this._log.error("failed to parse project from projectfile", projectFile, e);
        }
        return null;
    }

    getDownloadPath()
    {
        const customDownloadPath = this.get(this.DOWNLOAD_PATH);
        return customDownloadPath || app.getPath("downloads");
    }
}
export default new ElectronSettings(path.join(app.getPath("userData")));