frontend/app/vidispine/item/VidispineItem.ts (183 lines of code) (raw):

import VidispineItemTI from "./VidispineItem-ti"; import { createCheckers } from "ts-interface-checker"; interface MetadataValue { value: string; uuid?: string; user?: string; timestamp?: string; change?: string; } interface MetadataField { name: string; value: MetadataValue[]; uuid?: string; user?: string; timestamp?: string; change?: string; } interface MetadataGroup { name: string; field: MetadataField[]; } interface MetadataTimespan { field: MetadataField[]; group: MetadataGroup[]; start: string; end: string; } interface ItemMetadata { revision: string; timespan: MetadataTimespan[]; } interface ItemIF { metadata: ItemMetadata; id: string; } interface ItemResponse { item: ItemIF[]; } const { MetadataValue, MetadataField, MetadataGroup, MetadataTimespan, ItemMetadata, ItemIF, ItemResponse, } = createCheckers(VidispineItemTI); /** * Main vidispine item class. This defines a group of useful methods for retrieving metadata values from the overall * document structure */ class VidispineItem implements ItemIF { metadata: ItemMetadata; id: string; /** * constructs the class from a raw object * @param sourceObject, if this does not validate then VError is thrown */ constructor(sourceObject: any) { ItemIF.check(sourceObject); this.metadata = sourceObject.metadata; this.id = sourceObject.id; } /** * convenience method that calls getMetadataValuesInGroup with no group specified, i.e. finds the metadata values * for a field in the root group * @param forKey field name that you want to get metadata for * @return same as for getMetadataValuesInGroup */ getMetadataValues(forKey: string): Array<string> | undefined { return this.getMetadataValuesInGroup(forKey, undefined); } /** * returns an array of version numbers, if it is present in the retrieved metadata */ getVersions(): Array<number> | undefined { const maybeValues = this.getMetadataValues("__version"); return maybeValues ? (maybeValues .map((stringValue) => { try { return parseInt(stringValue); } catch (err) { console.warn("VS version was not a number"); return undefined; } }) .filter((maybeValue) => maybeValue !== undefined) as Array<number>) : undefined; } /** * returns the latest version number, if there is any version data present */ getLatestVersion(): number | undefined { const maybeVersions = this.getVersions(); const sortedVersions = maybeVersions ? maybeVersions.sort((a, b) => b - a) : undefined; return sortedVersions ? sortedVersions[0] : undefined; } /** * convenience method that calls getMetadataInGroup and strips out all excess information, simply returning * the values as an array of strings. * @param forKey the field name that you want to get metadata for * @param inGroup inGroup optionally, the name of a group to locate the field in * @return either an array of metadata values for the field (which can be empty) or undefined if the field or group * do not exist within the metadata response */ getMetadataValuesInGroup( forKey: string, inGroup: string | undefined ): string[] | undefined { const metavalues = this.getMetadataInGroup(forKey, inGroup); return metavalues ? metavalues.map((entry) => entry.value) : undefined; } /** * convenience method that will coalesce all values into a string joined by a comma separator * @param forKey */ getMetadataString(forKey: string): string | undefined { const possibleValues = this.getMetadataValuesInGroup(forKey, undefined); if (possibleValues) { return possibleValues.join(", "); } else { return undefined; } } /** * searches the default timespan for metadata entries metching the given key, optionally within the given group. * @param forKey the field name that you want to get metadata for * @param inGroup optionally, the name of a group to locate the field in * @return either an array of matching MetadataValue entries (which can be an empty array if the field exists with * no values) or undefined if the field or key do not exist within the metadata response */ getMetadataInGroup( forKey: string, inGroup: string | undefined ): MetadataValue[] | undefined { const timespan = this.getDefaultTimespan(); if (!timespan) return undefined; let fieldset: MetadataField[]; if (inGroup) { const groupref = this.getGroup(inGroup, timespan); if (!groupref) return undefined; fieldset = groupref.field; } else { fieldset = timespan.field; } const values = fieldset .filter((f) => f.name === forKey) .map((f) => f.value); if (values.length === 0) return undefined; return values.reduce((acc, elem) => acc.concat(...elem)); } /** * gets the group with the given name in the given timespan. * if multiple groups match, the first is returned and an error emitted to console * @param groupName name of the group to search for * @param timespan timespan to search in */ getGroup( groupName: string, timespan: MetadataTimespan ): MetadataGroup | undefined { const potentialGroups = timespan.group.filter((g) => g.name === groupName); if (potentialGroups.length == 0) { return undefined; } else if (potentialGroups.length > 1) { console.warn( `VidispineItem::getGroup - ${potentialGroups.length} groups were found for ${groupName}` ); } return potentialGroups[0]; } /** * returns a simple boolean indicating whether the item has a given group on it * @param groupName group to search for * @param timespan optional timspan to search in, if not set the default timespan is used */ hasGroup(groupName: string, timespan?: MetadataTimespan): boolean { const timespanForLookup = timespan ?? this.getDefaultTimespan(); if (timespanForLookup && timespanForLookup.group) { return ( timespanForLookup.group.filter((group) => group.name === groupName) .length > 0 ); } else { return false; } } /** * returns the "default" timespan: * - if there is only one timespan, that one * - if there are multiple timespans, the one that has a start and end at "-INF" and "+INF" respectively * - if there are multiple timespans running from "-INF" to "+INF" then a RangeError is thrown */ getDefaultTimespan(): MetadataTimespan | undefined { if (this.metadata.timespan.length === 0) { return undefined; } else if (this.metadata.timespan.length === 1) { return this.metadata.timespan[0]; } else { const potentialTimespans = this.metadata.timespan.filter( (timespan) => timespan.start === "-INF" && timespan.end === "+INF" ); if (potentialTimespans.length === 0) { return undefined; } else if (potentialTimespans.length === 1) { return potentialTimespans[0]; } else { const err = new RangeError(); err.message = "Multiple default timespans existed? This is incorrect."; throw err; } } } /** * returns an array of strings representing the names of the groups mentioned in the document. * This is an empty list if either the timespan does not exist or there are no group entries. */ getGroupNames(): string[] { const ts = this.getDefaultTimespan(); if (ts == undefined) return []; return ts.group.map((g) => g.name); } } /** * takes an untyped object from JSON.parse, validates it and returns an ItemResponse. * if it fails to validate as an ItemResponse, then an exception is thrown describing the syntax issue * @param content decoded json object * @returns the ItemResponse or raises an error */ function MakeItemResponse(content: object): ItemResponse { ItemResponse.check(content); return <ItemResponse>content; } /** * takes an untyped object from JSON.parse, validates it and returns an array of VidispineItems. * if it fails to validate as an ItemResponse, then an exception is thrown describing the syntax issue * @param content decoded json object * @returns an erray of VidispineItems (which might be empty) or throws an error */ function GetItems(content: object): Array<VidispineItem> { const itemresponse = MakeItemResponse(content); return itemresponse.item.map((rawitem) => new VidispineItem(rawitem)); } export type { ItemIF, MetadataValue, MetadataField, MetadataGroup, MetadataTimespan, ItemMetadata, }; export { GetItems, MakeItemResponse, VidispineItem };