forked from akai/readest
981579c255
* refactor(sync): kind-agnostic replica primitives Extract dict-only sync into shared primitives (registry, pull/apply orchestrator, persist env, schema allowlist) so other kinds can plug in. Companion changes: per-replica Storage Manager grouping, useReplicaPull boot-race recovery, manifest=null reconciliation on every boot pull, copyFile takes explicit srcBase + dstBase, settled- event helpers, lenient webDownload Content-Length (R2/S3 signed URLs commonly omit it), and generic "File" transfer toast copy any replica kind can share. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sync): cross-device custom font sync Plug the font replica adapter into the kind-agnostic primitives: font store gains replica wiring, custom font import publishes the replica row + queues a binary upload, and bootstrap registers the font adapter and download-complete handler. Includes legacy flat-path migration so pre-existing fonts sync without re-import, full @font-face activation on auto-download (load + mount the rule, mirroring manual import), and a fix to createCustomFont so contentId / bundleDir / byteSize survive the trip through addFont — otherwise import-time publish silently no-oped on missing contentId. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { FileSystem } from '@/types/system';
|
|
import { getFilename } from '@/utils/path';
|
|
import { md5, partialMD5 } from '@/utils/md5';
|
|
import { uniqueId } from '@/utils/misc';
|
|
import { CustomFont, CustomFontInfo } from '@/styles/fonts';
|
|
import { parseFontInfo } from '@/utils/font';
|
|
|
|
/**
|
|
* Build the cross-device content id for a font:
|
|
* `md5(partialMD5 ‖ byteSize ‖ filename)`. Same recipe shape as
|
|
* dictionary.computeReplicaId — keeps the kinds aligned.
|
|
*/
|
|
export const computeFontContentId = (
|
|
partialMd5: string,
|
|
byteSize: number,
|
|
filename: string,
|
|
): string => md5(`${partialMd5}|${byteSize}|${filename}`);
|
|
|
|
/**
|
|
* Import a font into the user's `Fonts` base under a per-font bundle dir
|
|
* (`<bundleDir>/<filename>`). The bundle dir is a fresh `uniqueId()` —
|
|
* matches the dictionary import pattern. Returns a CustomFontInfo with
|
|
* the relative path, contentId, bundleDir, and byteSize populated so
|
|
* the store can publish the replica row immediately.
|
|
*/
|
|
export async function importFont(
|
|
fs: FileSystem,
|
|
file?: string | File,
|
|
): Promise<CustomFontInfo | null> {
|
|
const bundleDir = uniqueId();
|
|
let filename: string;
|
|
let bytes: ArrayBuffer;
|
|
|
|
if (typeof file === 'string') {
|
|
const filePath = file;
|
|
const fileobj = await fs.openFile(filePath, 'None');
|
|
filename = fileobj.name || getFilename(filePath);
|
|
bytes = await fileobj.arrayBuffer();
|
|
} else if (file) {
|
|
filename = getFilename(file.name);
|
|
bytes = await file.arrayBuffer();
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
const fontPath = `${bundleDir}/${filename}`;
|
|
await fs.createDir(bundleDir, 'Fonts', true);
|
|
await fs.writeFile(fontPath, 'Fonts', bytes);
|
|
|
|
const fontFile = await fs.openFile(fontPath, 'Fonts');
|
|
const partialMd5 = await partialMD5(fontFile);
|
|
const byteSize = bytes.byteLength;
|
|
const contentId = computeFontContentId(partialMd5, byteSize, filename);
|
|
|
|
return {
|
|
path: fontPath,
|
|
bundleDir,
|
|
contentId,
|
|
byteSize,
|
|
...parseFontInfo(bytes, filename),
|
|
};
|
|
}
|
|
|
|
export async function deleteFont(fs: FileSystem, font: CustomFont): Promise<void> {
|
|
await fs.removeFile(font.path, 'Fonts');
|
|
// Also remove the per-font bundle dir if it's now empty. Legacy fonts
|
|
// without bundleDir live at the flat `Fonts/<filename>` path; nothing
|
|
// extra to clean up there.
|
|
if (font.bundleDir) {
|
|
try {
|
|
await fs.removeDir(font.bundleDir, 'Fonts', true);
|
|
} catch (err) {
|
|
console.warn('Failed to remove font bundleDir', font.bundleDir, err);
|
|
}
|
|
}
|
|
}
|