cables_dev/cables_electron/src/utils/projects_util.js
- import { SharedProjectsUtil, utilProvider } from "cables-shared-api";
- import path from "path";
- import sanitizeFileName from "sanitize-filename";
- import { app } from "electron";
- import pako from "pako";
- import crypto from "crypto";
- import jsonfile from "jsonfile";
- import fs from "fs";
- import settings from "../electron/electron_settings.js";
- import helper from "./helper_util.js";
- import cables from "../cables.js";
- import filesUtil from "./files_util.js";
- import opsUtil from "./ops_util.js";
-
- class ProjectsUtil extends SharedProjectsUtil
- {
- constructor(provider)
- {
- super(provider);
- this.CABLES_PROJECT_FILE_EXTENSION = "cables";
-
- this._dirInfos = null;
- this._projectOpDocs = null;
- }
-
- getAssetPath(projectId)
- {
- return cables.getAssetPath();
- }
-
- getAssetPathUrl(projectId)
- {
- return "./assets/";
- }
-
- getScreenShotPath(pId)
- {
- return path.join(app.getPath("userData"), "screenshots/");
- }
-
- getScreenShotFileName(proj, ext)
- {
- const screenShotPath = this.getScreenShotPath(proj.id);
- return path.join(screenShotPath, "/", filesUtil.realSanitizeFilename(proj.name) + "." + ext);
- }
-
- generateNewProject(owner)
- {
- if (!owner) owner = settings.getCurrentUser();
- const now = Date.now();
- const projectId = helper.generateRandomId();
- const shortId = helper.generateShortId(projectId, now);
- const randomize = settings.getUserSetting("randomizePatchName", false);
- const newProjectName = this.getNewProjectName(randomize);
-
- return {
- "_id": projectId,
- "shortId": shortId,
- "name": newProjectName,
- "description": "",
- "userId": owner._id,
- "cachedUsername": owner.username,
- "created": now,
- "updated": now,
- "visibility": "private",
- "ops": [],
- "settings": {
- "licence": "none"
- },
- "userList": [owner],
- "teams": [],
- "log": []
- };
- }
-
- getNewProjectName(randomize = false)
- {
- return "untitled";
- }
-
- getProjectOpDirs(project, includeOsDir = true, reverse = false, addLocalCoreIfPackaged = true)
- {
- let opsDirs = [];
-
- const projectDir = settings.getCurrentProjectDir();
- if (projectDir)
- {
- const currentDir = path.join(projectDir, "ops/");
- opsDirs.push(currentDir);
- }
-
- if (project && project.dirs && project.dirs.ops)
- {
- project.dirs.ops.forEach((dir) =>
- {
- if (projectDir && !path.isAbsolute(dir)) dir = path.join(projectDir, dir);
- opsDirs.push(dir);
- });
- }
- if (includeOsDir)
- {
- const osOpsDir = cables.getOsOpsDir();
- if (osOpsDir) opsDirs.push(osOpsDir);
- }
- if (addLocalCoreIfPackaged && !cables.isPackaged())
- {
- opsDirs.push(cables.getExtensionOpsPath());
- opsDirs.push(cables.getCoreOpsPath());
- }
- opsDirs = helper.uniqueArray(opsDirs);
- if (reverse) return opsDirs.reverse();
- return opsDirs;
- }
-
- isFixedPositionOpDir(dir)
- {
- const projectDir = settings.getCurrentProjectDir();
- if (projectDir) if (dir === path.join(projectDir, "ops/")) return true;
- if (dir === "./ops") return true;
- if (dir === cables.getOsOpsDir()) return true;
- if (cables.isPackaged()) return false;
- if (dir === cables.getExtensionOpsPath()) return true;
- return dir === cables.getCoreOpsPath();
- }
-
- getProjectFileName(project)
- {
- return sanitizeFileName(project.name).replace(/ /g, "_") + "." + this.CABLES_PROJECT_FILE_EXTENSION;
- }
-
- writeProjectToFile(projectFile, project = null, patch = null)
- {
- if (!project) project = this.generateNewProject();
- if (!project.ops) project.ops = [];
- if (patch && (patch.data || patch.dataB64))
- {
- try
- {
- let buf = patch.data;
- if (patch.dataB64) buf = Buffer.from(patch.dataB64, "base64");
-
- const qData = JSON.parse(pako.inflate(buf, { "to": "string" }));
- if (qData.ops) project.ops = qData.ops;
- if (qData.ui) project.ui = qData.ui;
- }
- catch (e)
- {
- this._log.error("patch save error/invalid data", e);
- return;
- }
- }
-
- // filter imported ops, so we do not save these to the database
- project.ops = project.ops.filter((op) =>
- {
- return !(op.storage && op.storage.blueprint);
- });
-
- project.name = path.basename(projectFile, "." + this.CABLES_PROJECT_FILE_EXTENSION);
- project.summary = project.summary || {};
- project.summary.title = project.name;
-
- project.opsHash = crypto
- .createHash("sha1")
- .update(JSON.stringify(project.ops))
- .digest("hex");
- project.buildInfo = settings.getBuildInfo();
- jsonfile.writeFileSync(projectFile, project, { "encoding": "utf-8", "spaces": 4 });
- settings.addToRecentProjects(projectFile, project);
- }
-
- getUsedAssetFilenames(project, includeLibraryAssets = false)
- {
- const fileNames = [];
- if (!project || !project.ops) return [];
- const assetPorts = this.getProjectAssetPorts(project, includeLibraryAssets);
- let urls = assetPorts.map((assetPort) => { return helper.pathToFileURL(assetPort.value, true); });
- urls.forEach((url) =>
- {
- let fullPath = helper.fileURLToPath(url, true);
- if (fullPath && fs.existsSync(fullPath))
- {
- fileNames.push(fullPath);
- }
- });
- return helper.uniqueArray(fileNames);
- }
-
- addOpDir(project, opDir, atTop = false)
- {
- if (!project.dirs) project.dirs = {};
- if (!project.dirs.ops) project.dirs.ops = [];
- if (atTop)
- {
- project.dirs.ops.unshift(opDir);
- }
- else
- {
- project.dirs.ops.push(opDir);
- }
- project.dirs.ops = helper.uniqueArray(project.dirs.ops);
- this.invalidateProjectCaches(opDir, atTop);
- return project;
- }
-
- removeOpDir(project, opDir)
- {
- if (!project.dirs) project.dirs = {};
- if (!project.dirs.ops) project.dirs.ops = [];
- project.dirs.ops = project.dirs.ops.filter((dirName) =>
- {
- return dirName !== opDir;
- });
- project.dirs.ops = helper.uniqueArray(project.dirs.ops);
- this.invalidateProjectCaches(opDir);
- return project;
- }
-
- getSummary(project)
- {
- if (!project) return {};
- return {
- "allowEdit": true,
- "title": project.name,
- "owner": settings.getCurrentUser(),
- "description": project.description,
- "licence": {
- "name": "No licence chosen"
- }
- };
- }
-
- getOpDirs(currentProject)
- {
- const dirs = this.getProjectOpDirs(currentProject, true);
- const dirInfos = [];
-
- dirs.forEach((dir) =>
- {
- const opJsons = helper.getFileNamesRecursive(dir, ".json");
- const opLocations = {};
- opJsons.forEach((jsonLocation) =>
- {
- const jsonName = path.basename(jsonLocation, ".json");
- if (opsUtil.isOpNameValid(jsonName))
- {
- opLocations[jsonName] = path.dirname(path.join(dir, jsonLocation));
- }
- });
- const opNames = Object.keys(opLocations);
-
- dirInfos.push({
- "dir": dir,
- "opLocations": opLocations,
- "numOps": opNames.length,
- "fixedPlace": this.isFixedPositionOpDir(dir)
- });
- });
- return dirInfos;
- }
-
- reorderOpDirs(currentProject, order)
- {
- const currentProjectFile = settings.getCurrentProjectFile();
- const newOrder = [];
- order.forEach((opDir) =>
- {
- if (fs.existsSync(opDir)) newOrder.push(opDir);
- });
- if (!currentProject.dirs) currentProject.dirs = {};
- if (!currentProject.dirs.ops) currentProject.dirs.ops = [];
- currentProject.dirs.ops = newOrder.filter((dir) => { return !this.isFixedPositionOpDir(dir); });
- currentProject.dirs.ops = helper.uniqueArray(currentProject.dirs.ops);
- this.writeProjectToFile(currentProjectFile, currentProject);
- this.invalidateProjectCaches();
- return currentProject;
- }
-
- getAbsoluteOpDirFromHierarchy(opName)
- {
- const currentProject = settings.getCurrentProject();
- if (!this._dirInfos)
- {
- this._log.debug("rebuilding opdir-cache, changed by:", opName);
- this._dirInfos = this.getOpDirs(currentProject);
- }
- if (!this._dirInfos) return this._opsUtil.getOpSourceNoHierarchy(opName);
-
- for (let i = 0; i < this._dirInfos.length; i++)
- {
- const dirInfo = this._dirInfos[i];
- const opNames = dirInfo.opLocations ? Object.keys(dirInfo.opLocations) : [];
- if (opNames.includes(opName))
- {
- return dirInfo.opLocations[opName];
- }
- }
- return this._opsUtil.getOpSourceNoHierarchy(opName);
- }
-
- invalidateProjectCaches()
- {
- this._dirInfos = null;
- this._projectOpDocs = null;
- }
-
- getOpDocsInProjectDirs(project, rebuildCache = false)
- {
- if (this._projectOpDocs && !rebuildCache) return this._projectOpDocs;
-
- const opDocs = {};
- const opDirs = this.getProjectOpDirs(project, true, false, false);
-
- opDirs.forEach((opDir) =>
- {
- if (fs.existsSync(opDir))
- {
- const opJsons = helper.getFilesRecursive(opDir, ".json");
- for (let jsonPath in opJsons)
- {
- const opName = path.basename(jsonPath, ".json");
- if (opsUtil.isOpNameValid(opName))
- {
- if (opDocs.hasOwnProperty(opName))
- {
- if (!opDocs[opName].hasOwnProperty("overrides")) opDocs[opName].overrides = [];
- opDocs[opName].overrides.push(path.join(opDir, path.dirname(jsonPath)));
- }
- else
- {
- try
- {
- const opDoc = jsonfile.readFileSync(path.join(opDir, jsonPath));
- opDoc.name = opName;
- opDocs[opName] = opDoc;
- }
- catch (e)
- {
- this._log.warn("failed to parse opDoc for", opName, "from", jsonPath);
- }
- }
- }
- }
- }
- });
- this._projectOpDocs = Object.values(opDocs);
- this._docsUtil.addOpsToLookup(this._projectOpDocs);
- return this._projectOpDocs;
- }
- }
- export default new ProjectsUtil(utilProvider);