feat: Implement basic general folder support (#2051)

- Related: #1748
This commit is contained in:
Dermot Duffy
2025-05-21 19:59:21 -07:00
committed by GitHub
parent 2eb0d9e35e
commit c6a4c8aea2
350 changed files with 12837 additions and 4509 deletions
+131
View File
@@ -0,0 +1,131 @@
import { format } from 'date-fns';
import { localize } from '../../localize/localize';
import { AdvancedCameraCardError } from '../../types';
import { errorToConsole } from '../../utils/basic';
import { downloadURL } from '../../utils/download';
import { homeAssistantSignPath } from '../../ha/sign-path';
import { ViewItem } from '../../view/item';
import { ViewItemClassifier } from '../../view/item-classifier';
import { ViewItemCapabilities } from '../../view/types';
import { CardViewAPI } from '../types';
enum ViewMediaSource {
Camera = 'camera',
Folder = 'folder',
}
export class ViewItemManager {
private _api: CardViewAPI;
constructor(api: CardViewAPI) {
this._api = api;
}
public getCapabilities(item: ViewItem): ViewItemCapabilities | null {
const source = this._getMediaSource(item);
if (source === ViewMediaSource.Camera && ViewItemClassifier.isMedia(item)) {
return this._api.getCameraManager().getMediaCapabilities(item);
}
if (source === ViewMediaSource.Folder) {
return this._api.getFoldersManager().getItemCapabilities(item);
}
return null;
}
public async download(item: ViewItem): Promise<boolean> {
try {
await this._download(item);
} catch (error: unknown) {
this._api.getMessageManager().setErrorIfHigherPriority(error);
return false;
}
return true;
}
public async favorite(item: ViewItem, favorite: boolean): Promise<void> {
const source = this._getMediaSource(item);
if (source === ViewMediaSource.Camera && ViewItemClassifier.isMedia(item)) {
return await this._api.getCameraManager().favoriteMedia(item, favorite);
}
/* istanbul ignore else: this path cannot be reached -- @preserve */
if (source === ViewMediaSource.Folder) {
return this._api.getFoldersManager().favorite(item, favorite);
}
}
private _getMediaSource(item: ViewItem): ViewMediaSource | null {
if (ViewItemClassifier.isMedia(item) && item.getCameraID()) {
return ViewMediaSource.Camera;
}
if (ViewItemClassifier.isFolder(item) || item.getFolder()) {
return ViewMediaSource.Folder;
}
/* istanbul ignore next: this path cannot be reached -- @preserve */
return null;
}
private async _download(item: ViewItem): Promise<void> {
const hass = this._api.getHASSManager().getHASS();
if (!hass) {
return;
}
const source = this._getMediaSource(item);
const endpoint =
source === ViewMediaSource.Camera && ViewItemClassifier.isMedia(item)
? await this._api.getCameraManager().getMediaDownloadPath(item)
: source === ViewMediaSource.Folder
? await this._api.getFoldersManager().getDownloadPath(item)
: null;
if (!endpoint) {
throw new AdvancedCameraCardError(localize('error.download_no_media'));
}
let finalURL = endpoint.endpoint;
if (endpoint.sign) {
let response: string | null | undefined;
try {
response = await homeAssistantSignPath(hass, endpoint.endpoint);
} catch (e) {
errorToConsole(e as Error);
}
if (!response) {
throw new AdvancedCameraCardError(localize('error.download_sign_failed'));
}
finalURL = response;
}
downloadURL(finalURL, this._generateDownloadFilename(item));
}
private _generateDownloadFilename(item: ViewItem): string {
const toFilename = (input: string): string => {
return input.toLowerCase().replaceAll(/[^a-z0-9]/gi, '-');
};
if (ViewItemClassifier.isMedia(item)) {
const cameraID = item.getCameraID();
const id = item.getID();
const startTime = item.getStartTime();
return (
(cameraID ? toFilename(cameraID) : 'media') +
(id ? `_${toFilename(id)}` : '') +
(startTime ? `_${format(startTime, `yyyy-MM-dd-HH-mm-ss`)}` : '') +
('.' + (item.getMediaType() === 'clip' ? 'mp4' : 'jpg'))
);
}
/* istanbul ignore else: this path cannot be reached -- @preserve */
if (ViewItemClassifier.isFolder(item)) {
return toFilename(item.getTitle() ?? 'media');
}
/* istanbul ignore next: this path cannot be reached -- @preserve */
return 'download';
}
}