import { HttpError } from "../errors"; import { findObject, openObject } from "./objects"; import { parseLfsPointer } from "./pointer"; import { chargeBandwidth } from "./quota"; import type { RepoRow } from "~/lib/types"; const IMAGE_TYPES_BY_EXTENSION: Record = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", ico: "image/x-icon", }; /** LFS content has no sniffed type, so images are typed by extension to stay previewable. */ export function lfsImageType(path: string): string { const extension = path.split(".").pop()?.toLowerCase() ?? ""; return IMAGE_TYPES_BY_EXTENSION[extension] ?? "application/octet-stream"; } /** Swaps an LFS pointer for its stored content, charging the owner's bandwidth; null when bytes are not a pointer. */ export async function lfsContentFor( repo: Pick, bytes: Uint8Array, ): Promise { const pointer = parseLfsPointer(bytes); if (!pointer) return null; const object = await openObject(repo, pointer.oid); if (!object) return null; try { await chargeBandwidth(repo.owner_id, object.size, repo.id); } catch (error) { await object.body.cancel(); if (error instanceof HttpError) return null; throw error; } return object; } export type LfsBlobInfo = { oid: string; size: number; stored: boolean }; /** For blob views: describes a pointer and loads its content only when small enough to render inline. */ export async function lfsBlobPreview( repo: Pick, bytes: Uint8Array, maxInlineBytes: number, ): Promise<{ info: LfsBlobInfo; content: Uint8Array | null } | null> { const pointer = parseLfsPointer(bytes); if (!pointer) return null; const found = await findObject(repo, pointer.oid); const info = { oid: pointer.oid, size: found?.size ?? pointer.size, stored: found !== null }; if (!found || found.size > maxInlineBytes) return { info, content: null }; const object = await lfsContentFor(repo, bytes); return { info, content: object ? new Uint8Array(await object.arrayBuffer()) : null }; }