a6cbcc273e
Made-with: Cursor
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
|
|
import { session } from 'electron';
|
|
|
|
import { asAssetId } from '../../shared/types/ids';
|
|
import type { ZipProjectStore } from '../project/zipStore';
|
|
|
|
/**
|
|
* Обслуживает `dnd://asset?...` — без этого `<img src="file://...">` в рендерере часто ломается.
|
|
*/
|
|
export function registerDndAssetProtocol(projectStore: ZipProjectStore): void {
|
|
session.defaultSession.protocol.handle('dnd', async (request) => {
|
|
const url = new URL(request.url);
|
|
if (url.hostname !== 'asset') {
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
const id = url.searchParams.get('id');
|
|
if (!id) {
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
const info = projectStore.getAssetReadInfo(asAssetId(id));
|
|
if (!info) {
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
try {
|
|
const stat = await fs.stat(info.absPath);
|
|
const total = stat.size;
|
|
const range = request.headers.get('range') ?? request.headers.get('Range');
|
|
|
|
if (range) {
|
|
const m = /^bytes=(\d+)-(\d+)?$/iu.exec(range.trim());
|
|
if (m) {
|
|
const start = Number(m[1]);
|
|
const endRaw = m[2] ? Number(m[2]) : total - 1;
|
|
const end = Math.min(endRaw, total - 1);
|
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start) {
|
|
return new Response(null, { status: 416 });
|
|
}
|
|
const len = end - start + 1;
|
|
const fh = await fs.open(info.absPath, 'r');
|
|
try {
|
|
const buf = Buffer.alloc(len);
|
|
await fh.read(buf, 0, len, start);
|
|
return new Response(buf, {
|
|
status: 206,
|
|
headers: {
|
|
'Content-Type': info.mime,
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Range': `bytes ${String(start)}-${String(end)}/${String(total)}`,
|
|
'Content-Length': String(len),
|
|
'Cache-Control': 'public, max-age=300',
|
|
},
|
|
});
|
|
} finally {
|
|
await fh.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
const buf = await fs.readFile(info.absPath);
|
|
return new Response(buf, {
|
|
headers: {
|
|
'Content-Type': info.mime,
|
|
'Accept-Ranges': 'bytes',
|
|
'Content-Length': String(buf.length),
|
|
'Cache-Control': 'public, max-age=300',
|
|
},
|
|
});
|
|
} catch {
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
});
|
|
}
|