Warning, /firebird/packages/firebird-ng/display/src/lib/services/file-open-router.service.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * Decides WHICH registered loader should open a file the user picked, dropped
0003 * or typed a URL for.
0004 *
0005 * This is the control that routes; it holds no format knowledge of its own.
0006 * A `.root` file is claimed by name from both registries - the same extension
0007 * carries a detector geometry or a podio event tree - so when the name is
0008 * ambiguous the router probes the file for neutral facts (its top-level keys)
0009 * and asks each loader `canLoadContent()`. The geometry loader knows what a
0010 * TGeoManager looks like, the event loader knows what an 'events' TTree looks
0011 * like, and neither piece of knowledge lives here.
0012 *
0013 * The router only decides. Loading is the caller's move, so a UI can open a
0014 * range picker for events but load geometry straight away.
0015 */
0016
0017 import { Injectable, inject } from '@angular/core';
0018 import type {
0019 DataSource,
0020 EventDataLoader,
0021 FileContentProbe,
0022 GeometryDataLoader,
0023 } from '@dexvis/firebird-core';
0024 import { sourceName } from '@dexvis/firebird-core/loaders';
0025 import { injectEventLoaders, injectGeometryLoaders } from '@dexvis/firebird-ng/api';
0026 import { RootFileService } from './root-file.service';
0027
0028 /** Where a source should go, and who takes it there. */
0029 export type FileRoute =
0030 | { kind: 'geometry'; loader: GeometryDataLoader; probe?: FileContentProbe }
0031 | { kind: 'events'; loader: EventDataLoader; probe?: FileContentProbe }
0032 | { kind: 'unknown'; probe?: FileContentProbe; message: string };
0033
0034 @Injectable({ providedIn: 'root' })
0035 export class FileOpenRouterService {
0036 private geometryLoaders = injectGeometryLoaders();
0037 private eventLoaders = injectEventLoaders();
0038 private rootFiles = inject(RootFileService);
0039
0040 async route(source: DataSource): Promise<FileRoute> {
0041 const name = sourceName(source);
0042 const geometryClaims = this.geometryLoaders.filter(loader => loader.canLoad(source));
0043 const eventClaims = this.eventLoaders.filter(loader => loader.canLoad(source));
0044
0045 // Unambiguous by name: one registry claims it, take the first taker
0046 if (geometryClaims.length && !eventClaims.length) {
0047 return { kind: 'geometry', loader: geometryClaims[0] };
0048 }
0049 if (eventClaims.length && !geometryClaims.length) {
0050 return { kind: 'events', loader: eventClaims[0] };
0051 }
0052 if (!geometryClaims.length && !eventClaims.length) {
0053 return { kind: 'unknown', message: `No loader claims '${name}'. ${this.knownFormats()}` };
0054 }
0055
0056 // Both claim the name. Only ROOT files are ambiguous this way, and only a
0057 // ROOT file can be probed - anything else is a registration mistake worth
0058 // reporting rather than guessing about.
0059 if (!name.split('?')[0].toLowerCase().endsWith('.root')) {
0060 return {
0061 kind: 'unknown',
0062 message:
0063 `Both a geometry and an event loader claim '${name}', and it cannot be ` +
0064 `inspected to tell them apart`,
0065 };
0066 }
0067
0068 const probe = await this.rootFiles.probe(source);
0069 const geometry = geometryClaims.find(loader => loader.canLoadContent?.(probe));
0070 if (geometry) return { kind: 'geometry', loader: geometry, probe };
0071 const events = eventClaims.find(loader => loader.canLoadContent?.(probe));
0072 if (events) return { kind: 'events', loader: events, probe };
0073
0074 return { kind: 'unknown', probe, message: this.describeUnrecognized(name, probe) };
0075 }
0076
0077 private describeUnrecognized(name: string, probe: FileContentProbe): string {
0078 const contents = probe.entries
0079 .filter(entry => entry.name !== 'StreamerInfo')
0080 .map(entry => `${entry.name} (${entry.className})`)
0081 .join(', ');
0082 return (
0083 `'${name}' is a ROOT file, but nothing in it is recognized` +
0084 (contents ? `. It holds: ${contents}` : ' — it looks empty') +
0085 `. Firebird reads detector geometry (TGeoManager) and podio event trees ('events').`
0086 );
0087 }
0088
0089 private knownFormats(): string {
0090 const all = [...this.geometryLoaders, ...this.eventLoaders];
0091 return `Known formats: ${all.map(l => `${l.meta.label} (${l.meta.fileExtensions.join(', ')})`).join('; ')}`;
0092 }
0093 }