Files
workinf_Blender_Wasm/tools/web/native-preferences.mjs
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

71 lines
4.3 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import crypto from 'node:crypto';
const profile = path.join(process.env.WEB_BLENDER_PROFILE_ROOT ?? path.join(os.homedir(), '.local', 'share', 'web-blender', '5.2'), 'config');
const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
export function nativeProfileDirectories(){
const result={};
for(const name of ['scripts','extensions','datafiles']){
const directory=path.join(path.dirname(profile),name);fs.mkdirSync(directory,{recursive:true,mode:0o700});result[name]=directory;
}
return result;
}
function readSaved() {
const file = path.join(profile, 'userpref.blend');
if (!fs.existsSync(file)) return null;
const stat = fs.lstatSync(file);
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16 * 1024 * 1024) throw new Error('PREFERENCES_FILE_INVALID');
return fs.readFileSync(file);
}
export function prepareNativePreferences(workspace) {
const config = path.join(workspace, 'config');
if(fs.existsSync(config)){const stat=fs.lstatSync(config);if(!stat.isDirectory()||stat.isSymbolicLink())throw new Error('PREFERENCES_DIRECTORY_INVALID');}else fs.mkdirSync(config,{mode:0o700});
for(const name of ['userpref.blend','startup.blend']){const file=path.join(config,name);if(fs.existsSync(file)){const stat=fs.lstatSync(file);if(!stat.isFile()||stat.isSymbolicLink())throw new Error('PREFERENCES_WORKSPACE_FILE_INVALID');}}
const bytes = readSaved();
if (bytes) fs.writeFileSync(path.join(config, 'userpref.blend'), bytes);
const startup=path.join(profile,'startup.blend');let startupSha256=null;
if(fs.existsSync(startup)){
const stat=fs.lstatSync(startup);if(!stat.isFile()||stat.isSymbolicLink()||stat.size>256*1024*1024)throw new Error('STARTUP_PROFILE_INVALID');
const data=fs.readFileSync(startup);startupSha256=digest(data);fs.writeFileSync(path.join(config,'startup.blend'),data);
}
fs.writeFileSync(path.join(workspace, 'preferences-base.json'), JSON.stringify({sha256: bytes ? digest(bytes) : null,startupSha256}));
return config;
}
export function persistNativePreferences(workspace, artifact) {
fs.mkdirSync(profile, {recursive: true, mode: 0o700});
const lock = path.join(profile, '.save-lock');
try { fs.mkdirSync(lock); } catch { throw new Error('PREFERENCES_SAVE_BUSY_RETRY'); }
try {
const base = JSON.parse(fs.readFileSync(path.join(workspace, 'preferences-base.json'), 'utf8'));
const previous = readSaved();
if ((previous ? digest(previous) : null) !== base.sha256) throw new Error('PREFERENCES_CHANGED_IN_ANOTHER_SESSION_RECONNECT');
const bytes = fs.readFileSync(path.join(workspace, artifact.id));
if (bytes.length > 16 * 1024 * 1024 || digest(bytes) !== artifact.sha256) throw new Error('PREFERENCES_ARTIFACT_INVALID');
const temporary = path.join(profile, crypto.randomUUID() + '.tmp');
try {
fs.writeFileSync(temporary, bytes, {mode: 0o600, flag: 'wx'});
fs.renameSync(temporary, path.join(profile, 'userpref.blend'));
} finally { fs.rmSync(temporary, {force: true}); }
fs.writeFileSync(path.join(workspace, 'preferences-base.json'), JSON.stringify({...base,sha256: artifact.sha256}));
} finally { fs.rmdirSync(lock); }
}
export function persistNativeStartup(workspace){
fs.mkdirSync(profile,{recursive:true,mode:0o700});const lock=path.join(profile,'.save-lock');
try{fs.mkdirSync(lock);}catch{throw new Error('PREFERENCES_SAVE_BUSY_RETRY');}
try{
const baseFile=path.join(workspace,'preferences-base.json'),base=JSON.parse(fs.readFileSync(baseFile,'utf8'));
const destination=path.join(profile,'startup.blend'),source=path.join(workspace,'config','startup.blend');
const current=fs.existsSync(destination)?digest(fs.readFileSync(destination)):null;
if(current!==(base.startupSha256??null))throw new Error('STARTUP_CHANGED_IN_ANOTHER_SESSION_RECONNECT');
const stat=fs.lstatSync(source);if(!stat.isFile()||stat.isSymbolicLink()||stat.size>256*1024*1024)throw new Error('STARTUP_PROFILE_INVALID');
const data=fs.readFileSync(source),temporary=path.join(profile,crypto.randomUUID()+'.tmp');
try{fs.writeFileSync(temporary,data,{mode:0o600,flag:'wx'});fs.renameSync(temporary,destination);}finally{fs.rmSync(temporary,{force:true});}
fs.writeFileSync(baseFile,JSON.stringify({...base,startupSha256:digest(data)}));
}finally{fs.rmdirSync(lock);}
}