Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions packages/server/src/managers/GraphicsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,52 @@ export class GraphicsStoreNS {
this.destroyed = true
if (this.checkInterVal !== undefined) clearInterval(this.checkInterVal)
}
/** Find a manifest file in a folder */
private async findManifestFile(graphicsFolder: string): Promise<string> {
/**
* Find a manifest file in a folder.
*
* A folder MAY hold several manifest files, and the specification is explicit
* that they are then "interpreted as multiple, independent Graphics" — a
* package of Graphics sharing resources such as images and fonts. So the first
* file readdir() happens to return is not an acceptable answer: it hands every
* caller whichever Graphic sorts first, and the id check in getGraphicInfo()
* then rejects the folder for every other Graphic in it.
*
* When the caller knows which Graphic it wants, match on the manifest id.
*/
private async findManifestFile(graphicsFolder: string, wantedId?: string): Promise<string> {
const files = await fs.promises.readdir(graphicsFolder, {
withFileTypes: true,
})

const candidates: string[] = []
for (const file of files) {
if (
file.isFile() &&
(file.name.endsWith('.ograf.json') || // Current v1 requirement, as of 2025-07-13
file.name.endsWith('.ograf') || // File name from 2025-06-13 to 2025-07-13
file.name === 'manifest.json') // Legacy, initial manifest file name
) {
return path.join(graphicsFolder, file.name)
candidates.push(path.join(graphicsFolder, file.name))
}
}

if (candidates.length === 0) {
throw new Error(`No OGraf manifest found in folder ${graphicsFolder}`)
}

if (wantedId !== undefined) {
for (const candidate of candidates) {
try {
const manifest = JSON.parse(await fs.promises.readFile(candidate, 'utf8')) as GraphicsManifest
if (manifest?.id === wantedId) return candidate
} catch {
// An unreadable or malformed manifest is not the one being looked for.
// It may still be the right answer for a caller that did not ask by id.
}
}
}

throw new Error(`No OGraf manifest found in folder ${graphicsFolder}`)
return candidates[0]
}
async listGraphics(config: ConfigOptions): Promise<ServerApi.components['schemas']['GraphicListInfo'][]> {
const graphics: ServerApi.components['schemas']['GraphicListInfo'][] = []
Expand Down Expand Up @@ -103,7 +132,9 @@ export class GraphicsStoreNS {
return undefined
}

const manifestFilePath = await this.findManifestFile(fullFolderPath)
// Several Graphics may share this folder, so ask for the one that was
// actually requested rather than whichever manifest is found first.
const manifestFilePath = await this.findManifestFile(fullFolderPath, id)

const pStat = fs.promises.stat(manifestFilePath)

Expand Down
Loading