Skip to content

Commit

Permalink
refactor: Refactor entity register manager (#1597)
Browse files Browse the repository at this point in the history
  • Loading branch information
dermotduffy authored Sep 29, 2024
1 parent 24603af commit 02fb6e1
Show file tree
Hide file tree
Showing 28 changed files with 290 additions and 91 deletions.
4 changes: 2 additions & 2 deletions src/camera-manager/browse-media/camera.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { localize } from '../../localize/localize';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { Entity } from '../../utils/ha/entity-registry/types';
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
import { Entity } from '../../utils/ha/registry/entity/types';
import { Camera, CameraInitializationOptions } from '../camera';
import { CameraInitializationError } from '../error';

Expand Down
2 changes: 1 addition & 1 deletion src/camera-manager/browse-media/engine-browse-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
MEDIA_CLASS_VIDEO,
RichBrowseMedia,
} from '../../utils/ha/browse-media/types';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
import { ResolvedMediaCache, resolveMedia } from '../../utils/ha/resolved-media';
import { ViewMedia } from '../../view/media';
import { RequestCache } from '../cache';
Expand Down
9 changes: 3 additions & 6 deletions src/camera-manager/engine-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { CameraConfig } from '../config/types';
import { localize } from '../localize/localize';
import { BrowseMediaManager } from '../utils/ha/browse-media/browse-media-manager';
import { BrowseMedia } from '../utils/ha/browse-media/types';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { Entity } from '../utils/ha/entity-registry/types';
import { EntityRegistryManager } from '../utils/ha/registry/entity';
import { ResolvedMediaCache } from '../utils/ha/resolved-media';
import { MemoryRequestCache, RecordingSegmentsCache, RequestCache } from './cache';
import { CameraManagerEngine } from './engine';
Expand Down Expand Up @@ -81,10 +80,8 @@ export class CameraManagerEngineFactory {
const cameraEntity = getCameraEntityFromConfig(cameraConfig);

if (cameraEntity) {
let entity: Entity | null;
try {
entity = await this._entityRegistryManager.getEntity(hass, cameraEntity);
} catch (e) {
const entity = await this._entityRegistryManager.getEntity(hass, cameraEntity);
if (!entity) {
// If the camera is not in the registry, but is in the HA states it is
// assumed to be a generic camera.
if (hass.states[cameraEntity]) {
Expand Down
9 changes: 4 additions & 5 deletions src/camera-manager/frigate/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { CameraConfig } from '../../config/types';
import { localize } from '../../localize/localize';
import { PTZCapabilities, PTZMovementType } from '../../types';
import { errorToConsole } from '../../utils/basic';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { Entity } from '../../utils/ha/entity-registry/types';
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
import { Entity } from '../../utils/ha/registry/entity/types';
import { Camera, CameraInitializationOptions } from '../camera';
import { Capabilities } from '../capabilities';
import { CameraManagerEngine } from '../engine';
Expand Down Expand Up @@ -67,9 +67,8 @@ export class FrigateCamera extends Camera {
// Entity information is required if the Frigate camera name is missing, or
// if the entity requires automatic resolution of motion/occupancy sensors.
if (cameraEntity && (!hasCameraName || hasAutoTriggers)) {
try {
entity = await entityRegistryManager.getEntity(hass, cameraEntity);
} catch (e) {
entity = await entityRegistryManager.getEntity(hass, cameraEntity);
if (!entity) {
throw new CameraInitializationError(localize('error.no_camera_entity'), config);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/camera-manager/frigate/engine-frigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
runWhenIdleIfSupported,
} from '../../utils/basic';
import { getEntityTitle } from '../../utils/ha';
import { EntityRegistryManager } from '../../utils/ha/entity-registry';
import { EntityRegistryManager } from '../../utils/ha/registry/entity';
import { ViewMedia } from '../../view/media';
import { ViewMediaClassifier } from '../../view/media-classifier';
import { RecordingSegmentsCache, RequestCache } from '../cache';
Expand Down
14 changes: 9 additions & 5 deletions src/card-controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { LovelaceCardEditor } from '@dermotduffy/custom-card-helpers';
import { ReactiveController } from 'lit';
import { CameraManager } from '../camera-manager/manager';
import { FrigateCardConfig } from '../config/types';
import { EntityRegistryManager } from '../utils/ha/entity-registry';
import { EntityCache } from '../utils/ha/entity-registry/cache';
import {
createEntityRegistryCache,
EntityRegistryManager,
} from '../utils/ha/registry/entity';
import { ResolvedMediaCache } from '../utils/ha/resolved-media';
import { ActionsManager } from './actions/actions-manager';
import { DefaultManager } from './default-manager';
import { AutomationsManager } from './automations-manager';
import { CameraURLManager } from './camera-url-manager';
import {
Expand All @@ -17,12 +18,14 @@ import {
} from './card-element-manager';
import { ConditionsManager, ConditionsManagerListener } from './conditions-manager';
import { ConfigManager } from './config/config-manager';
import { DefaultManager } from './default-manager';
import { DownloadManager } from './download-manager';
import { ExpandManager } from './expand-manager';
import { FullscreenManager } from './fullscreen-manager';
import { HASSManager } from './hass/hass-manager';
import { InitializationManager } from './initialization-manager';
import { InteractionManager } from './interaction-manager';
import { KeyboardStateManager } from './keyboard-state-manager';
import { MediaLoadedInfoManager } from './media-info-manager';
import { MediaPlayerManager } from './media-player-manager';
import { MessageManager } from './message-manager';
Expand Down Expand Up @@ -57,7 +60,6 @@ import {
CardViewAPI,
} from './types';
import { ViewManager } from './view/view-manager';
import { KeyboardStateManager } from './keyboard-state-manager';

export class CardController
implements
Expand Down Expand Up @@ -88,7 +90,9 @@ export class CardController
{
// These properties may be used in the construction of 'managers' (and should
// be created first).
protected _entityRegistryManager = new EntityRegistryManager(new EntityCache());
protected _entityRegistryManager = new EntityRegistryManager(
createEntityRegistryCache(),
);
protected _resolvedMediaCache = new ResolvedMediaCache();

protected _actionsManager = new ActionsManager(this);
Expand Down
2 changes: 1 addition & 1 deletion src/card-controller/media-player-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CameraConfig, FrigateCardConfig } from '../config/types';
import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA } from '../const';
import { localize } from '../localize/localize';
import { errorToConsole } from '../utils/basic';
import { Entity } from '../utils/ha/entity-registry/types';
import { Entity } from '../utils/ha/registry/entity/types';
import { supportsFeature } from '../utils/ha/update';
import { ViewMedia } from '../view/media';
import { ViewMediaClassifier } from '../view/media-classifier';
Expand Down
2 changes: 1 addition & 1 deletion src/card-controller/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CameraManager } from '../camera-manager/manager';
import type { Automation } from '../config/types';
import type { EntityRegistryManager } from '../utils/ha/entity-registry';
import type { EntityRegistryManager } from '../utils/ha/registry/entity';
import type { ResolvedMediaCache } from '../utils/ha/resolved-media';
import type { ActionsManager } from './actions/actions-manager';
import type { AutomationsManager } from './automations-manager';
Expand Down
2 changes: 1 addition & 1 deletion src/components/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MenuController } from '../components-lib/menu-controller.js';
import type { MenuConfig, MenuItem } from '../config/types.js';
import menuStyle from '../scss/menu.scss';
import { frigateCardHasAction } from '../utils/action.js';
import { EntityRegistryManager } from '../utils/ha/entity-registry/index.js';
import { EntityRegistryManager } from '../utils/ha/registry/entity/index.js';
import './submenu.js';

@customElement('frigate-card-menu')
Expand Down
2 changes: 1 addition & 1 deletion src/components/submenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
stopEventFromActivatingCardWideActions,
} from '../utils/action.js';
import { isHassDifferent, refreshDynamicStateParameters } from '../utils/ha';
import { EntityRegistryManager } from '../utils/ha/entity-registry/index.js';
import { getEntityStateTranslation } from '../utils/ha/entity-state-translation.js';
import { EntityRegistryManager } from '../utils/ha/registry/entity/index.js';
import { domainIcon } from '../utils/icons/domain-icon.js';

@customElement('frigate-card-submenu')
Expand Down
3 changes: 2 additions & 1 deletion src/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import pkg from '../../package.json';
import { RawFrigateCardConfig } from '../config/types';
import { getLanguage } from '../localize/localize';
import { DeviceList, getAllDevices } from './ha/device-registry';
import { getAllDevices } from './ha/registry/device';
import { DeviceList } from './ha/registry/device/types';

type FrigateVersions = Record<string, string>;

Expand Down
2 changes: 1 addition & 1 deletion src/utils/ha/entity-state-translation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computeDomain, HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import { Entity } from './entity-registry/types';
import { Entity } from './registry/entity/types';

/**
* Get the translation of an entity state. Inspired by:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Entity } from './types.js';
export class RegistryCache<T> {
protected _cache: Map<string, T> = new Map();
protected _keyCallback: (_data: T) => string;

export class EntityCache {
protected _cache: Map<string, Entity> = new Map();
constructor(keyCallback: (_data: T) => string) {
this._keyCallback = keyCallback;
}

/**
* Determine if the cache has a given entity_id.
Expand All @@ -17,11 +20,7 @@ export class EntityCache {
* @param func A callback function that returns a boolean.
* @returns The first matching value.
*/
// public getFirstMatch(func: (arg: T) => boolean): T | null {
// return [...this._cache.values()].find(func) ?? null;
// }

public getMatches(func: (arg: Entity) => boolean): Entity[] {
public getMatches(func: (arg: T) => boolean): T[] {
return [...this._cache.values()].filter(func);
}

Expand All @@ -30,16 +29,16 @@ export class EntityCache {
* @param id The entity id.
* @returns The entity for this id.
*/
public get(id: string): Entity | undefined {
return this._cache.get(id);
public get(id: string): T | null {
return this._cache.get(id) ?? null;
}

/**
* Add a given entity to the cache.
* @param input The entity.
*/
public set(input: Entity | Entity[]): void {
const _set = (entity: Entity) => this._cache.set(entity.entity_id, entity);
public add(input: T | T[]): void {
const _set = (arg: T) => this._cache.set(this._keyCallback(arg), arg);

if (Array.isArray(input)) {
input.forEach(_set);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { z } from 'zod';
import { homeAssistantWSRequest } from '.';

const deviceSchema = z.object({
model: z.string().nullable(),
config_entries: z.string().array(),
manufacturer: z.string().nullable(),
});
const deviceListSchema = deviceSchema.array();
export type DeviceList = z.infer<typeof deviceListSchema>;
import { homeAssistantWSRequest } from '../..';
import { DeviceList, deviceListSchema } from './types';

/**
* Get a list of all entities from the entity registry. May throw.
Expand Down
12 changes: 12 additions & 0 deletions src/utils/ha/registry/device/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from 'zod';

export const deviceSchema = z.object({
id: z.string(),
model: z.string().nullable(),
config_entries: z.string().array(),
manufacturer: z.string().nullable(),
});
export type Device = z.infer<typeof deviceSchema>;

export const deviceListSchema = deviceSchema.array();
export type DeviceList = z.infer<typeof deviceListSchema>;
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
import { homeAssistantWSRequest } from '..';
import { EntityCache } from './cache';
import { homeAssistantWSRequest } from '../..';
import { Entity, EntityList, entitySchema, entityListSchema } from './types.js';
import { RegistryCache } from '../cache';

export const createEntityRegistryCache = (): RegistryCache<Entity> => {
return new RegistryCache<Entity>((entity) => entity.entity_id);
};

// This class manages interactions with entities, caching results and fetching
// as necessary. Some calls require every entity to be fetched, which may be
// non-trivial in size (after which it is cached forever).
// non-trivial in size (after which they are cached forever).

export class EntityRegistryManager {
protected _cache: EntityCache;
protected _cache: RegistryCache<Entity>;
protected _fetchedEntityList = false;

constructor(cache: EntityCache) {
constructor(cache: RegistryCache<Entity>) {
this._cache = cache;
}

Expand All @@ -21,11 +25,16 @@ export class EntityRegistryManager {
return cachedEntity;
}

const entity = await homeAssistantWSRequest<Entity>(hass, entitySchema, {
type: 'config/entity_registry/get',
entity_id: entityID,
});
this._cache.set(entity);
let entity: Entity | null = null;
try {
entity = await homeAssistantWSRequest<Entity>(hass, entitySchema, {
type: 'config/entity_registry/get',
entity_id: entityID,
});
} catch {
return null;
}
this._cache.add(entity);
return entity;
}

Expand All @@ -43,15 +52,11 @@ export class EntityRegistryManager {
): Promise<Map<string, Entity>> {
const output: Map<string, Entity> = new Map();
const _storeEntity = async (entityID: string): Promise<void> => {
let entity: Entity | null = null;
try {
entity = await this.getEntity(hass, entityID);
} catch {
const entity = await this.getEntity(hass, entityID);

if (entity) {
// When asked to fetch multiple entities, ignore missing entities (they
// will just not feature in the output).
return;
}
if (entity) {
output.set(entityID, entity);
}
};
Expand All @@ -66,7 +71,7 @@ export class EntityRegistryManager {
const entityList = await homeAssistantWSRequest<EntityList>(hass, entityListSchema, {
type: 'config/entity_registry/list',
});
this._cache.set(entityList);
this._cache.add(entityList);
this._fetchedEntityList = true;
}
}
File renamed without changes.
6 changes: 3 additions & 3 deletions tests/camera-manager/browse-media/camera.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { describe, expect, it } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { BrowseMediaCamera } from '../../../src/camera-manager/browse-media/camera';
import { CameraManagerEngine } from '../../../src/camera-manager/engine';
import { EntityRegistryManager } from '../../../src/utils/ha/entity-registry';
import { Entity } from '../../../src/utils/ha/entity-registry/types';
import { createCameraConfig, createHASS } from '../../test-utils';
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
import { EntityRegistryManager } from '../../../src/utils/ha/registry/entity';
import { Entity } from '../../../src/utils/ha/registry/entity/types';
import { createCameraConfig, createHASS } from '../../test-utils';

describe('BrowseMediaCamera', () => {
describe('should initialize', () => {
Expand Down
Loading

0 comments on commit 02fb6e1

Please sign in to comment.