Warning, /firebird/firebird-ng/src/app/services/data-model.service.ts is written in an unsupported language. File is not indexed.
0001 import {Injectable, linkedSignal, signal} from "@angular/core";
0002 import { Event } from "../model/event";
0003 import { HttpClient } from "@angular/common/http";
0004 import { UrlService } from "./url.service";
0005 import { DataExchange } from "../model/data-exchange";
0006 import { fetchTextFile, loadJSONFileEvents, loadZipFileEvents } from "../utils/data-fetching.utils";
0007
0008 /**
0009 * Service for loading and managing event/entry data in Firebird.
0010 *
0011 * This service encapsulates loading EDM4eic data (converted to DEX),
0012 * as well as loading existing Firebird DEX data from JSON or ZIP.
0013 * It stores a list of entries and the currently selected entry
0014 * as Angular signals.
0015 */
0016 @Injectable({
0017 providedIn: 'root'
0018 })
0019 export class DataModelService {
0020
0021 /**
0022 * Signal holding the list of loaded entries (events).
0023 * Each Entry corresponds to one event's data in Firebird Dex format.
0024 */
0025 public entries = signal<Event[]>([]);
0026
0027 /**
0028 * Signal holding the currently selected entry (event).
0029 */
0030 public currentEntry = linkedSignal(() => {
0031 if(this.entries().length > 0 ) {
0032 return this.entries()[0]
0033 }
0034 return null;
0035 });
0036
0037 /**
0038 * Constructor that injects services needed for resolving URLs and making HTTP requests.
0039 * @param urlService - Service used for building/transforming URLs
0040 * @param http - Angular HttpClient for making network requests (currently not used directly here)
0041 */
0042 constructor(
0043 private urlService: UrlService,
0044 private http: HttpClient
0045 ) {}
0046
0047 /**
0048 * Checks if an unknown object is valid "Firebird DEX" format by
0049 * verifying if it has a `"type": "firebird-dex-json"` property.
0050 *
0051 * @param obj - The object to inspect.
0052 * @returns True if the object has a `"type"` property equal to `"firebird-dex-json"`, otherwise false.
0053 */
0054 public isFirebirdDex(obj: unknown): boolean {
0055 return (
0056 typeof obj === "object" &&
0057 obj !== null &&
0058 "type" in obj &&
0059 (obj as any)["type"] === "firebird-dex-json"
0060 );
0061 }
0062
0063 /**
0064 * Loads EDM4eic data from a given URL by calling the Firebird convert endpoint,
0065 * returning it in Firebird DEX format.
0066 *
0067 * @param url - The original location of the EDM4eic ROOT file (or other source).
0068 * @param entryNames - Comma-separated entry indices (default "0"). Passed to the converter service.
0069 * @returns A Promise that resolves to a DataExchange object or null if there's an error.
0070 */
0071 async loadRootData(url: string, entryNames: string = "0"): Promise<DataExchange | null> {
0072 try {
0073 // Early exit if no URL is provided
0074 if (!url) {
0075 console.log("[DataModelService.loadEdm4EicData] No data source specified.");
0076 return null;
0077 }
0078
0079 // Let urlService build the final convert URL
0080 let finalUrl = this.urlService.resolveConvertUrl(url, "edm4eic", entryNames);
0081 console.log(`[DataModelService.loadDexData] Fetching: ${finalUrl}`);
0082
0083 // Load the text from that URL
0084 const jsonData = await fetchTextFile(finalUrl);
0085
0086 // Parse JSON
0087 const dexData = JSON.parse(jsonData);
0088
0089 // Validate format
0090 if (!this.isFirebirdDex(dexData)) {
0091 console.error("[DataModelService.loadDexData] The JSON does not conform to Firebird DEX JSON format.");
0092 return null;
0093 }
0094
0095 // Build DataExchange structure
0096 let data = DataExchange.fromDexObj(dexData);
0097 console.log(data);
0098 return data;
0099
0100 } catch (error) {
0101 // Log errors
0102 console.error(`[DataModelService.loadEdm4EicData] Failed to load data: ${error}`);
0103 console.log("Default config will be used");
0104 } finally {
0105 // No final cleanup needed right now
0106 }
0107 return null;
0108 }
0109
0110 /**
0111 * Loads a Firebird DEX JSON or ZIP file from a specified URL.
0112 *
0113 * @param url - The URL of the .firebird.json or .zip file.
0114 * @returns A Promise that resolves to a DataExchange object or null if there's an error.
0115 */
0116 async loadDexData(url: string): Promise<DataExchange | null> {
0117 try {
0118 // If no URL is provided, exit.
0119 if (!url) {
0120 console.log("[DataModelService.loadDexData] No data source specified.");
0121 return null;
0122 }
0123
0124 // Basic extension check (not strictly required)
0125 if (
0126 !url.endsWith("firebird.json") &&
0127 !url.endsWith("firebird.json.zip") &&
0128 !url.endsWith("firebird.zip")
0129 ) {
0130 console.log("[DataModelService.loadDexData] Wrong extension or file type.");
0131 }
0132
0133 // Resolve local aliases or relative paths
0134 let finalUrl = url;
0135 if (url.startsWith("asset://")) {
0136 // Transform 'asset://' URLs to 'assets/' paths. This removes the leading slash
0137 // intentionally to support deployment in the /firebird subdirectory.
0138 finalUrl = "assets/" + url.substring("asset://".length);
0139 } else if (!url.startsWith("http://") && !url.startsWith("https://")) {
0140 finalUrl = this.urlService.resolveDownloadUrl(url);
0141 }
0142
0143 let dexData = {};
0144 console.log(`[DataModelService.loadDexData] Loading: ${finalUrl}`);
0145
0146 // Decide which loader to call based on file extension
0147 if (finalUrl.endsWith("zip")) {
0148 // Load from ZIP
0149 dexData = await loadZipFileEvents(finalUrl);
0150 } else {
0151 // Load from raw JSON
0152 dexData = await loadJSONFileEvents(finalUrl);
0153 }
0154
0155 // Validate Firebird DEX structure
0156 if (!this.isFirebirdDex(dexData)) {
0157 console.error("[DataModelService.loadDexData] The JSON does not conform to Firebird DEX JSON format.");
0158 return null;
0159 }
0160
0161 console.log(`[DataModelService.loadDexData] Deserializing from DEX`);
0162 let data = DataExchange.fromDexObj(dexData);
0163 console.log(data);
0164
0165 // Extract entry names/IDs for debugging or usage
0166 const entryNames = data.events.map((entry) => entry.id);
0167
0168 // Update service signals with the newly loaded entries
0169 if (dexData) {
0170 this.entries.set(data.events);
0171 if (this.entries().length > 0) {
0172 // If at least one entry is present, automatically set the first as current
0173 this.setCurrentEntry(this.entries()[0]);
0174 }
0175 }
0176
0177 return data;
0178
0179 } catch (error) {
0180 console.error(`[DataModelService.loadDexData] Failed to load data: ${error}`);
0181 console.log(`[DataModelService.loadDexData] Default config will be used`);
0182 } finally {
0183 // No final cleanup needed right now
0184 }
0185 return null;
0186 }
0187
0188 /**
0189 * Sets the currently selected entry (event) to the provided `Entry`.
0190 *
0191 * @param entry - The Entry object to be marked as current.
0192 */
0193 setCurrentEntry(entry: Event): void {
0194 console.log(`[DataModelService.setCurrentEntry] Setting event: ${entry.id}`);
0195 this.currentEntry.set(entry);
0196 }
0197
0198 /**
0199 * Finds and sets the current entry by its name (i.e. `entry.id`).
0200 *
0201 * @param name - The string name or ID of the entry to set as current.
0202 */
0203 setCurrentEntryByName(name: string): void {
0204 // Look up the first Entry whose 'id' matches the provided name
0205 const found = this.entries().find((entry) => entry.id === name);
0206
0207 // If found, update currentEntry; otherwise log a warning.
0208 if (found) {
0209 this.setCurrentEntry(found);
0210 } else {
0211 console.warn(`[DataModelService] setCurrentEntryByName: Entry with id='${name}' not found.`);
0212 }
0213 }
0214 }