Files
workinf_Blender_Wasm/tools/web/native-session-workspace.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

30 lines
3.3 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import {randomUUID} from 'node:crypto';
import {nativeProfileDirectories} from './native-preferences.mjs';
const leases=new Set();
export function nativeSessionWorkspaceRoot(){const root=path.join(path.dirname(nativeProfileDirectories().datafiles),'sessions');fs.mkdirSync(root,{recursive:true,mode:0o700});return fs.realpathSync(root);}
const failure=(code,statusCode=409)=>Object.assign(new Error(code),{statusCode});
export function reserveNativeSessionWorkspace(request,checkUsage){
const key=request.desktop?.preserveLayout?request.desktop?.recoveryKey:undefined;
if(key!==undefined&&(typeof key!=='string'||!/^[a-f0-9-]{36}$/.test(key)))throw failure('SESSION_RECOVERY_KEY_INVALID',400);
const persistent=Boolean(key),workspace=persistent?path.join(nativeSessionWorkspaceRoot(),key):fs.mkdtempSync(path.join(os.tmpdir(),'blender-session-'));
if(leases.has(workspace))throw failure('SESSION_RECOVERY_OWNER_ACTIVE');
leases.add(workspace);
try{
if(fs.existsSync(workspace)){const stat=fs.lstatSync(workspace);if(!stat.isDirectory()||stat.isSymbolicLink())throw failure('SESSION_WORKSPACE_INVALID');}else fs.mkdirSync(workspace,{mode:0o700});
checkUsage(workspace);
const marker=path.join(workspace,'.session-slot.json'),retained=persistent&&fs.existsSync(marker);
if(retained){const stat=fs.lstatSync(marker);if(!stat.isFile()||stat.isSymbolicLink()||stat.size>4096)throw failure('SESSION_WORKSPACE_MARKER_INVALID');const saved=JSON.parse(fs.readFileSync(marker,'utf8'));if(saved.schemaVersion!==1||saved.key!==key)throw failure('SESSION_WORKSPACE_OWNER_INVALID');}
return {workspace,persistent,retained,key,release:()=>leases.delete(workspace)};
}catch(error){leases.delete(workspace);if(!persistent)fs.rmSync(workspace,{recursive:true,force:true});throw error;}
}
export function prepareNativeSessionWorkspace(lease){
const {workspace}=lease,orphans=fs.readdirSync(workspace).filter(name=>name==='source.blend'||/^(result|resources|preferences|project)-[0-9]{8}\.(blend|zip)$/.test(name));
if(orphans.length){const retired=path.join(workspace,'.retired');if(fs.existsSync(retired)){const stat=fs.lstatSync(retired);if(!stat.isDirectory()||stat.isSymbolicLink())throw failure('SESSION_RETENTION_DIRECTORY_INVALID');}else fs.mkdirSync(retired,{mode:0o700});const archive=path.join(retired,randomUUID());fs.mkdirSync(archive,{mode:0o700});for(const name of orphans)fs.renameSync(path.join(workspace,name),path.join(archive,name));}
for(const directory of ['commands','responses','events','runtime'])fs.rmSync(path.join(workspace,directory),{recursive:true,force:true});
for(const name of ['request.json','session-ready.json','desktop-state.json','input-ack.json','input-error.json','pen-input.json','pen-output.json','startup-ready.json','desktop-recovery.json','screen.png','screen-meta.json','screen.tmp','stop'])for(const file of [name,name+'.tmp'])fs.rmSync(path.join(workspace,file),{force:true});
}
export function markNativeSessionWorkspace(lease){if(!lease.persistent)return;const target=path.join(lease.workspace,'.session-slot.json'),temporary=target+'.'+randomUUID()+'.tmp';fs.writeFileSync(temporary,JSON.stringify({schemaVersion:1,key:lease.key}),{flag:'wx',mode:0o600});fs.renameSync(temporary,target);}