Cache media-metadata requests.

This commit is contained in:
Dermot Duffy
2023-02-18 19:20:10 -08:00
parent 90b4d249d4
commit 425782f347
6 changed files with 122 additions and 53 deletions
+4 -1
View File
@@ -22,6 +22,8 @@ import {
CameraConfigs, CameraConfigs,
Engine, Engine,
CameraEndpoints, CameraEndpoints,
MediaMetadataQuery,
MediaMetadataQueryResultsMap,
} from './types'; } from './types';
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000; export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
@@ -106,7 +108,8 @@ export interface CameraManagerEngine {
getMediaMetadata( getMediaMetadata(
hass: HomeAssistant, hass: HomeAssistant,
cameras: CameraConfigs, cameras: CameraConfigs,
): Promise<MediaMetadata | null>; query: MediaMetadataQuery,
): Promise<MediaMetadataQueryResultsMap | null>;
getCameraMetadata( getCameraMetadata(
hass: HomeAssistant, hass: HomeAssistant,
+39 -14
View File
@@ -4,7 +4,7 @@ import endOfHour from 'date-fns/endOfHour';
import startOfHour from 'date-fns/startOfHour'; import startOfHour from 'date-fns/startOfHour';
import { CameraConfig, CardWideConfig } from '../../types'; import { CameraConfig, CardWideConfig } from '../../types';
import { ViewMedia } from '../../view/media'; import { ViewMedia } from '../../view/media';
import { RequestCache, RecordingSegmentsCache } from '../cache'; import { RecordingSegmentsCache, RequestCache } from '../cache';
import { import {
CameraManagerEngine, CameraManagerEngine,
CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT,
@@ -19,9 +19,6 @@ import {
EventQuery, EventQuery,
EventQueryResults, EventQueryResults,
EventQueryResultsMap, EventQueryResultsMap,
FrigateEventQueryResults,
FrigateRecordingQueryResults,
FrigateRecordingSegmentsQueryResults,
MediaMetadata, MediaMetadata,
PartialEventQuery, PartialEventQuery,
PartialRecordingQuery, PartialRecordingQuery,
@@ -40,8 +37,16 @@ import {
CameraConfigs, CameraConfigs,
CameraEndpoints, CameraEndpoints,
CameraEndpoint, CameraEndpoint,
MediaMetadataQuery,
MediaMetadataQueryResults,
MediaMetadataQueryResultsMap,
} from '../types'; } from '../types';
import { FrigateRecording } from './types'; import {
FrigateEventQueryResults,
FrigateRecordingQueryResults,
FrigateRecordingSegmentsQueryResults,
FrigateRecording,
} from './types';
import { import {
getEvents, getEvents,
getEventSummary, getEventSummary,
@@ -76,6 +81,7 @@ import { GenericCameraManagerEngine } from '../generic/engine-generic';
const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60; const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60;
const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60; const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60;
const MEDIA_METADATA_REQUEST_CACHE_AGE_SECONDS = 60;
const CAMERA_BIRDSEYE = 'birdseye' as const; const CAMERA_BIRDSEYE = 'birdseye' as const;
@@ -802,14 +808,26 @@ export class FrigateCameraManagerEngine
public async getMediaMetadata( public async getMediaMetadata(
hass: HomeAssistant, hass: HomeAssistant,
cameras: CameraConfigs, cameras: CameraConfigs,
): Promise<MediaMetadata | null> { query: MediaMetadataQuery,
): Promise<MediaMetadataQueryResultsMap | null> {
const output: MediaMetadataQueryResultsMap = new Map();
if (this._requestCache.has(query)) {
const cachedResult = <MediaMetadataQueryResults | null>(
this._requestCache.get(query)
);
if (cachedResult) {
output.set(query, cachedResult as MediaMetadataQueryResults);
return output;
}
}
const what: Set<string> = new Set(); const what: Set<string> = new Set();
const where: Set<string> = new Set(); const where: Set<string> = new Set();
const days: Set<string> = new Set(); const days: Set<string> = new Set();
const instances = this._buildInstanceToCameraIDMapFromQuery( const instances = this._buildInstanceToCameraIDMapFromQuery(
cameras, cameras,
new Set(cameras.keys()), query.cameraIDs,
); );
const processEventSummary = async ( const processEventSummary = async (
@@ -865,14 +883,21 @@ export class FrigateCameraManagerEngine
})(), })(),
); );
if (!what.size && !where.size && !days.size) { const result: MediaMetadataQueryResults = {
return null; type: QueryResultsType.MediaMetadata,
} engine: Engine.Frigate,
return { metadata: {
...(what.size && { what: what }), ...(what.size && { what: what }),
...(where.size && { where: where }), ...(where.size && { where: where }),
...(days.size && { days: days }), ...(days.size && { days: days }),
},
expiry: add(new Date(), { seconds: MEDIA_METADATA_REQUEST_CACHE_AGE_SECONDS }),
cached: false,
}; };
this._requestCache.set(query, { ...result, cached: true }, result.expiry);
output.set(query, result);
return output;
} }
/** /**
+28
View File
@@ -1,5 +1,11 @@
import { z } from 'zod'; import { z } from 'zod';
import { dayToDate } from '../../utils/basic'; import { dayToDate } from '../../utils/basic';
import {
Engine,
EventQueryResults,
RecordingQueryResults,
RecordingSegmentsQueryResults,
} from '../types';
const dayStringToDate = (arg: unknown): Date | unknown => { const dayStringToDate = (arg: unknown): Date | unknown => {
return typeof arg === 'string' ? dayToDate(arg) : arg; return typeof arg === 'string' ? dayToDate(arg) : arg;
@@ -66,3 +72,25 @@ export const eventSummarySchema = z
}) })
.array(); .array();
export type EventSummary = z.infer<typeof eventSummarySchema>; export type EventSummary = z.infer<typeof eventSummarySchema>;
// ==============================
// Frigate concrete query results
// ==============================
export interface FrigateEventQueryResults extends EventQueryResults {
engine: Engine.Frigate;
instanceID: string;
events: FrigateEvent[];
}
export interface FrigateRecordingQueryResults extends RecordingQueryResults {
engine: Engine.Frigate;
instanceID: string;
recordings: FrigateRecording[];
}
export interface FrigateRecordingSegmentsQueryResults
extends RecordingSegmentsQueryResults {
engine: Engine.Frigate;
instanceID: string;
}
+4 -1
View File
@@ -23,6 +23,8 @@ import {
CameraManagerCameraCapabilities, CameraManagerCameraCapabilities,
Engine, Engine,
CameraEndpoints, CameraEndpoints,
MediaMetadataQuery,
MediaMetadataQueryResultsMap,
} from '../types'; } from '../types';
import { getEntityIcon, getEntityTitle } from '../../utils/ha'; import { getEntityIcon, getEntityTitle } from '../../utils/ha';
import { EntityRegistryManager } from '../../utils/ha/entity-registry'; import { EntityRegistryManager } from '../../utils/ha/entity-registry';
@@ -139,7 +141,8 @@ export class GenericCameraManagerEngine implements CameraManagerEngine {
public async getMediaMetadata( public async getMediaMetadata(
_hass: HomeAssistant, _hass: HomeAssistant,
_cameras: CameraConfigs, _cameras: CameraConfigs,
): Promise<MediaMetadata | null> { _query: MediaMetadataQuery,
): Promise<MediaMetadataQueryResultsMap | null> {
return null; return null;
} }
+34 -19
View File
@@ -31,6 +31,8 @@ import {
ResultsMap, ResultsMap,
CameraEndpoints, CameraEndpoints,
Engine, Engine,
MediaMetadataQuery,
MediaMetadataQueryResults,
} from './types.js'; } from './types.js';
import orderBy from 'lodash-es/orderBy'; import orderBy from 'lodash-es/orderBy';
import { CameraManagerEngineFactory } from './engine-factory.js'; import { CameraManagerEngineFactory } from './engine-factory.js';
@@ -62,6 +64,11 @@ class QueryClassifier {
): query is RecordingSegmentsQuery { ): query is RecordingSegmentsQuery {
return query.type === QueryType.RecordingSegments; return query.type === QueryType.RecordingSegments;
} }
public static isMediaMetadataQuery(
query: DataQuery | PartialDataQuery,
): query is MediaMetadataQuery {
return query.type === QueryType.MediaMetadata;
}
} }
class QueryResultClassifier { class QueryResultClassifier {
@@ -80,6 +87,11 @@ class QueryResultClassifier {
): queryResults is RecordingSegmentsQueryResults { ): queryResults is RecordingSegmentsQueryResults {
return queryResults.type === QueryResultsType.RecordingSegments; return queryResults.type === QueryResultsType.RecordingSegments;
} }
public static isMediaMetadataQuery(
queryResults: QueryResults,
): queryResults is MediaMetadataQueryResults {
return queryResults.type === QueryResultsType.MediaMetadata;
}
} }
export interface ExtendedMediaQueryResult<T extends MediaQuery> { export interface ExtendedMediaQueryResult<T extends MediaQuery> {
@@ -268,27 +280,24 @@ export class CameraManager {
const where: Set<string> = new Set(); const where: Set<string> = new Set();
const days: Set<string> = new Set(); const days: Set<string> = new Set();
const engines = this._store.getAllEngines(); const query: MediaMetadataQuery = {
type: QueryType.MediaMetadata,
const processMetadata = async (engine: CameraManagerEngine): Promise<void> => { cameraIDs: this._store.getCameraIDs(),
const engineMetadata = await engine.getMediaMetadata(
hass,
this._store.getCameras(),
);
if (engineMetadata) {
if (engineMetadata.what) {
engineMetadata.what.forEach(what.add, what);
}
if (engineMetadata.where) {
engineMetadata.where.forEach(where.add, where);
}
if (engineMetadata.days) {
engineMetadata.days.forEach(days.add, days);
}
}
}; };
await allPromises(engines, processMetadata); const results = await this._handleQuery(hass, query);
for (const [query, result] of results?.entries() ?? []) {
if (result.metadata.what) {
result.metadata.what.forEach(what.add, what);
}
if (result.metadata.where) {
result.metadata.where.forEach(where.add, where);
}
if (result.metadata.days) {
result.metadata.days.forEach(days.add, days);
}
}
if (!what.size && !where.size && !days.size) { if (!what.size && !where.size && !days.size) {
return null; return null;
@@ -569,6 +578,12 @@ export class CameraManager {
this._store.getCameras(), this._store.getCameras(),
query, query,
)) as Map<QT, QueryReturnType<QT>> | null; )) as Map<QT, QueryReturnType<QT>> | null;
} else if (QueryClassifier.isMediaMetadataQuery(query)) {
engineResult = (await engine.getMediaMetadata(
hass,
this._store.getCameras(),
query,
)) as Map<QT, QueryReturnType<QT>> | null;
} }
engineResult?.forEach((value, key) => results.set(key, value)); engineResult?.forEach((value, key) => results.set(key, value));
+13 -18
View File
@@ -1,6 +1,5 @@
import { CameraConfig, FrigateCardView } from '../types'; import { CameraConfig, FrigateCardView } from '../types';
import { ViewMedia } from '../view/media'; import { ViewMedia } from '../view/media';
import { FrigateEvent, FrigateRecording } from './frigate/types';
// ==== // ====
// Base // Base
@@ -10,12 +9,14 @@ export enum QueryType {
Event = 'event-query', Event = 'event-query',
Recording = 'recording-query', Recording = 'recording-query',
RecordingSegments = 'recording-segments-query', RecordingSegments = 'recording-segments-query',
MediaMetadata = 'media-metadata',
} }
export enum QueryResultsType { export enum QueryResultsType {
Event = 'event-results', Event = 'event-results',
Recording = 'recording-results', Recording = 'recording-results',
RecordingSegments = 'recording-segments-results', RecordingSegments = 'recording-segments-results',
MediaMetadata = 'media-metadata-results',
} }
export enum Engine { export enum Engine {
@@ -65,6 +66,8 @@ export type QueryReturnType<QT> = QT extends EventQuery
? RecordingQueryResults ? RecordingQueryResults
: QT extends RecordingSegmentsQuery : QT extends RecordingSegmentsQuery
? RecordingSegmentsQueryResults ? RecordingSegmentsQueryResults
: QT extends MediaMetadataQuery
? MediaMetadataQueryResults
: never; : never;
export type PartialQueryConcreteType<PQT> = PQT extends PartialEventQuery export type PartialQueryConcreteType<PQT> = PQT extends PartialEventQuery
? EventQuery ? EventQuery
@@ -78,6 +81,7 @@ export type ResultsMap<QT> = Map<QT, QueryReturnType<QT>>;
export type EventQueryResultsMap = ResultsMap<EventQuery>; export type EventQueryResultsMap = ResultsMap<EventQuery>;
export type RecordingQueryResultsMap = ResultsMap<RecordingQuery>; export type RecordingQueryResultsMap = ResultsMap<RecordingQuery>;
export type RecordingSegmentsQueryResultsMap = ResultsMap<RecordingSegmentsQuery>; export type RecordingSegmentsQueryResultsMap = ResultsMap<RecordingSegmentsQuery>;
export type MediaMetadataQueryResultsMap = ResultsMap<MediaMetadataQuery>;
export interface MediaMetadata { export interface MediaMetadata {
where?: Set<string>; where?: Set<string>;
@@ -176,24 +180,15 @@ export interface RecordingSegmentsQueryResults extends QueryResults {
segments: RecordingSegment[]; segments: RecordingSegment[];
} }
// ======================== // ====================
// Frigate concrete results // Media metadata Query
// ======================== // ====================
export interface FrigateEventQueryResults extends EventQueryResults { export interface MediaMetadataQuery extends DataQuery {
engine: Engine.Frigate; type: QueryType.MediaMetadata;
instanceID: string;
events: FrigateEvent[];
} }
export interface FrigateRecordingQueryResults extends RecordingQueryResults { export interface MediaMetadataQueryResults extends QueryResults {
engine: Engine.Frigate; type: QueryResultsType.MediaMetadata;
instanceID: string; metadata: MediaMetadata;
recordings: FrigateRecording[];
}
export interface FrigateRecordingSegmentsQueryResults
extends RecordingSegmentsQueryResults {
engine: Engine.Frigate;
instanceID: string;
} }