Cache media-metadata requests.
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user