feat: F5.5 large binary files as blobs on server F
From 16 KB a picture's or binary file's entry names its bytes by their SHA-256 instead of holding them; the bytes are on F, content-addressed (GET/POST /blobs with the session's grant, from the editor's origin, at most 16 MiB). A document's blob side says where they are: the editor's fetches and uploads, the room thread's asks F's main thread, and a start waits for the ones asked for; without a side everything stays in the document. Text stays Y.Text. A blob whose bytes are not there is never written back empty. A blob F has not got yet is asked for again a few times, and a running room reads late bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import * as Y from 'yjs';
|
||||
import type { Id, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core';
|
||||
import { VirtualFileSystem as FileSystem } from '@mdg-filius/core';
|
||||
import { blobSha, blobSideOf } from './blobs.js';
|
||||
import { fileEntry, fileSystemOf, inOrder, ROOTS } from './projectDoc.js';
|
||||
|
||||
export function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
|
||||
@@ -29,11 +30,21 @@ export function replaceText(text: Y.Text, next: string): void {
|
||||
if (next.length - start - end > 0) text.insert(start, next.slice(start, next.length - end));
|
||||
}
|
||||
|
||||
/** Whether a document entry holds the content. */
|
||||
/**
|
||||
* Whether a document entry holds the content (a blob by its bytes where they
|
||||
* are here, else by its name).
|
||||
*/
|
||||
function holds(entry: Y.Map<unknown>, content: VirtualFileContent): boolean {
|
||||
if (entry.get('kind') !== content.kind) return false;
|
||||
if (content.kind === 'directory' || content.kind === 'text') return true;
|
||||
return sameBytes(entry.get('data') as Uint8Array, content.data) && entry.get('mediaType') === content.mediaType;
|
||||
if (entry.get('mediaType') !== content.mediaType) return false;
|
||||
const blob = entry.get('blob');
|
||||
if (typeof blob !== 'string') return sameBytes(entry.get('data') as Uint8Array, content.data);
|
||||
const held = blobSideOf(entry.doc)?.bytes(blob);
|
||||
// A blob whose bytes are not here was read as no bytes: written back so, that is no change (it would empty the file).
|
||||
if (held === undefined && content.data.length === 0) return true;
|
||||
if (entry.get('size') !== content.data.length) return false;
|
||||
return held === undefined ? blobSha(content.data) === blob : sameBytes(held, content.data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,6 +53,7 @@ function holds(entry: Y.Map<unknown>, content: VirtualFileContent): boolean {
|
||||
* longer the file system's (a renamed entry), numbered anew.
|
||||
*/
|
||||
export function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem): void {
|
||||
const blobs = blobSideOf(node.doc);
|
||||
let files = node.get('files') as Y.Map<Y.Map<unknown>> | undefined;
|
||||
const wanted: [string, VirtualFileContent][] = [];
|
||||
const walk = (folder: string): void => {
|
||||
@@ -62,9 +74,9 @@ export function writeFiles(node: Y.Map<unknown>, fileSystem: VirtualFileSystem):
|
||||
for (const [path, content] of wanted) {
|
||||
const entry = files.get(path);
|
||||
if (entry === undefined) {
|
||||
files.set(path, fileEntry(order++, content));
|
||||
files.set(path, fileEntry(order++, content, blobs));
|
||||
} else if (!holds(entry, content)) {
|
||||
files.set(path, fileEntry(entry.get('order') as number, content));
|
||||
files.set(path, fileEntry(entry.get('order') as number, content, blobs));
|
||||
} else if (content.kind === 'text') {
|
||||
replaceText(entry.get('text') as Y.Text, content.text);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user