Skip to content

Commit

Permalink
feat: auto refresh on person thumbnail
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 committed Sep 11, 2023
1 parent a15780e commit 80cbf53
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 137 deletions.
2 changes: 2 additions & 0 deletions server/src/domain/communication/communication.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export const ICommunicationRepository = 'ICommunicationRepository';
export enum CommunicationEvent {
UPLOAD_SUCCESS = 'on_upload_success',
ASSET_DELETE = 'on_asset_delete',
PERSON_THUMBNAIL = 'on_person_thumbnail',
SERVER_VERSION = 'on_server_version',
}

export interface ICommunicationRepository {
Expand Down
23 changes: 19 additions & 4 deletions server/src/domain/job/job.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,20 @@ export class JobService {
}
break;

case JobName.GENERATE_FACE_THUMBNAIL:
const { assetId, personId } = item.data;
const [asset] = await this.assetRepository.getByIds([assetId]);
if (asset) {
this.communicationRepository.send(CommunicationEvent.PERSON_THUMBNAIL, asset.ownerId, { personId, assetId });
}
break;

case JobName.GENERATE_JPEG_THUMBNAIL: {
await this.jobRepository.queue({ name: JobName.GENERATE_WEBP_THUMBNAIL, data: item.data });
await this.jobRepository.queue({ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: item.data });
await this.jobRepository.queue({ name: JobName.CLASSIFY_IMAGE, data: item.data });
await this.jobRepository.queue({ name: JobName.ENCODE_CLIP, data: item.data });
await this.jobRepository.queue({ name: JobName.RECOGNIZE_FACES, data: item.data });
if (item.data.source !== 'upload') {
break;
}

const [asset] = await this.assetRepository.getByIds([item.data.id]);
if (asset) {
Expand All @@ -181,10 +186,20 @@ export class JobService {
} else if (asset.livePhotoVideoId) {
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.livePhotoVideoId } });
}
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
}
break;
}

case JobName.GENERATE_WEBP_THUMBNAIL: {
if (item.data.source !== 'upload') {
break;
}

const [asset] = await this.assetRepository.getByIds([item.data.id]);
if (asset) {
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
}
}
}

// In addition to the above jobs, all of these should queue `SEARCH_INDEX_ASSET`
Expand Down
34 changes: 0 additions & 34 deletions server/src/infra/communication.gateway.ts

This file was deleted.

3 changes: 1 addition & 2 deletions server/src/infra/infra.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { BullModule } from '@nestjs/bullmq';
import { Global, Module, Provider } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CommunicationGateway } from './communication.gateway';
import { databaseConfig } from './database.config';
import { databaseEntities } from './entities';
import { bullConfig, bullQueues } from './infra.config';
Expand Down Expand Up @@ -90,7 +89,7 @@ const providers: Provider[] = [
BullModule.forRoot(bullConfig),
BullModule.registerQueue(...bullQueues),
],
providers: [...providers, CommunicationGateway],
providers: [...providers],
exports: [...providers, BullModule],
})
export class InfraModule {}
41 changes: 34 additions & 7 deletions server/src/infra/repositories/communication.repository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
import { CommunicationEvent } from '@app/domain';
import { Injectable } from '@nestjs/common';
import { CommunicationGateway } from '../communication.gateway';
import { AuthService, CommunicationEvent, ICommunicationRepository, serverVersion } from '@app/domain';
import { Logger } from '@nestjs/common';
import { OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@Injectable()
export class CommunicationRepository {
constructor(private ws: CommunicationGateway) {}
@WebSocketGateway({ cors: true })
export class CommunicationRepository implements OnGatewayConnection, OnGatewayDisconnect, ICommunicationRepository {
private logger = new Logger(CommunicationRepository.name);

constructor(private authService: AuthService) {}

@WebSocketServer() server!: Server;

async handleConnection(client: Socket) {
try {
this.logger.log(`New websocket connection: ${client.id}`);
const user = await this.authService.validate(client.request.headers, {});
if (user) {
client.join(user.id);
this.send(CommunicationEvent.SERVER_VERSION, user.id, serverVersion);
} else {
client.emit('error', 'unauthorized');
client.disconnect();
}
} catch (e) {
client.emit('error', 'unauthorized');
client.disconnect();
}
}

handleDisconnect(client: Socket) {
client.leave(client.nsp.name);
this.logger.log(`Client ${client.id} disconnected from Websocket`);
}

send(event: CommunicationEvent, userId: string, data: any) {
this.ws.server.to(userId).emit(event, JSON.stringify(data));
this.server.to(userId).emit(event, JSON.stringify(data));
}
}
74 changes: 27 additions & 47 deletions web/src/lib/components/shared-components/status-box.svelte
Original file line number Diff line number Diff line change
@@ -1,52 +1,36 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment';
import { locale } from '$lib/stores/preferences.store';
import { websocketStore } from '$lib/stores/websocket';
import { ServerInfoResponseDto, api } from '@api';
import { onDestroy } from 'svelte';
import Cloud from 'svelte-material-icons/Cloud.svelte';
import Dns from 'svelte-material-icons/Dns.svelte';
import LoadingSpinner from './loading-spinner.svelte';
import { api, ServerInfoResponseDto } from '@api';
import { asByteUnitString } from '../../utils/byte-units';
import { locale } from '$lib/stores/preferences.store';
import LoadingSpinner from './loading-spinner.svelte';
let isServerOk = true;
let serverVersion = '';
let serverInfo: ServerInfoResponseDto;
let pingServerInterval: NodeJS.Timer;
const { serverVersion, connected } = websocketStore;
onMount(async () => {
try {
const { data: version } = await api.serverInfoApi.getServerVersion();
let serverInfo: ServerInfoResponseDto;
serverVersion = `v${version.major}.${version.minor}.${version.patch}`;
$: version = $serverVersion ? `v${$serverVersion.major}.${$serverVersion.minor}.${$serverVersion.patch}` : null;
$: usedPercentage = Math.round((serverInfo?.diskUseRaw / serverInfo?.diskSizeRaw) * 100);
const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
serverInfo = serverInfoRes;
getStorageUsagePercentage();
const refresh = async () => {
try {
const { data } = await api.serverInfoApi.getServerInfo();
serverInfo = data;
} catch (e) {
console.log('Error [StatusBox] [onMount]');
isServerOk = false;
}
};
pingServerInterval = setInterval(async () => {
try {
const { data: pingReponse } = await api.serverInfoApi.pingServer();
if (pingReponse.res === 'pong') isServerOk = true;
else isServerOk = false;
const { data: serverInfoRes } = await api.serverInfoApi.getServerInfo();
serverInfo = serverInfoRes;
} catch (e) {
console.log('Error [StatusBox] [pingServerInterval]', e);
isServerOk = false;
}
}, 10000);
});
onDestroy(() => clearInterval(pingServerInterval));
let interval: number;
if (browser) {
interval = window.setInterval(() => refresh(), 10_000);
}
const getStorageUsagePercentage = () => {
return Math.round((serverInfo?.diskUseRaw / serverInfo?.diskSizeRaw) * 100);
};
onDestroy(() => clearInterval(interval));
</script>

<div class="dark:text-immich-dark-fg">
Expand All @@ -61,7 +45,7 @@
<!-- style={`width: ${$downloadAssets[fileName]}%`} -->
<div
class="h-[7px] rounded-full bg-immich-primary dark:bg-immich-dark-primary"
style="width: {getStorageUsagePercentage()}%"
style="width: {usedPercentage}%"
/>
</div>
<p class="text-xs">
Expand All @@ -88,7 +72,7 @@
<div class="mt-2 flex justify-between justify-items-center">
<p>Status</p>

{#if isServerOk}
{#if $connected}
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">Online</p>
{:else}
<p class="font-medium text-red-500">Offline</p>
Expand All @@ -97,16 +81,12 @@

<div class="mt-2 flex justify-between justify-items-center">
<p>Version</p>
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
{serverVersion}
</p>
{#if $connected && version}
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">{version}</p>
{:else}
<p class="font-medium text-red-500">Unknown</p>
{/if}
</div>
</div>
</div>
<!-- <div>
<hr class="ml-5 my-4" />
</div>
<button class="text-xs ml-5 underline hover:cursor-pointer text-immich-primary" on:click={() => goto('/changelog')}
>Changelog</button
> -->
</div>
15 changes: 6 additions & 9 deletions web/src/lib/stores/assets.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { api, AssetApiGetTimeBucketsRequest, AssetResponseDto } from '@api';
import { debounce } from 'lodash-es';
import { throttle } from 'lodash-es';
import { DateTime } from 'luxon';
import { Unsubscriber, writable } from 'svelte/store';
import { handleError } from '../utils/handle-error';
Expand Down Expand Up @@ -77,13 +77,14 @@ export class AssetStore {
websocketStore.onUploadSuccess.subscribe((value) => {
if (value) {
this.pendingChanges.push({ type: 'add', value });
this.debouncer();
this.processPendingChanges();
}
}),

websocketStore.onAssetDelete.subscribe((value) => {
if (value) {
this.pendingChanges.push({ type: 'remove', value });
this.debouncer();
this.processPendingChanges();
}
}),
);
Expand All @@ -95,7 +96,7 @@ export class AssetStore {
}
}

debouncer = debounce(() => {
processPendingChanges = throttle(() => {
for (const { type, value } of this.pendingChanges) {
switch (type) {
case 'add':
Expand All @@ -109,11 +110,7 @@ export class AssetStore {

this.pendingChanges = [];
this.emit(true);
}, 2000);

flush() {
this.debouncer.flush();
}
}, 10_000);

async init(viewport: Viewport) {
this.initialized = false;
Expand Down
51 changes: 26 additions & 25 deletions web/src/lib/stores/websocket.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,44 @@
import { io, Socket } from 'socket.io-client';
import type { AssetResponseDto } from '../../api/open-api';
import type { AssetResponseDto, ServerVersionResponseDto } from '@api';
import { io } from 'socket.io-client';
import { writable } from 'svelte/store';

let websocket: Socket;

function initWebsocketStore() {
const onUploadSuccess = writable<AssetResponseDto>();
const onAssetDelete = writable<string>();

return {
onUploadSuccess,
onAssetDelete,
};
interface PersonFace {
assetId: string;
personId: string;
}

export const websocketStore = initWebsocketStore();
export const websocketStore = {
onUploadSuccess: writable<AssetResponseDto>(),
onAssetDelete: writable<string>(),
onPersonThumbnail: writable<PersonFace>(),
serverVersion: writable<ServerVersionResponseDto>(),
connected: writable<boolean>(false),
};

export const openWebsocketConnection = () => {
try {
websocket = io('', {
const websocket = io('', {
path: '/api/socket.io',
transports: ['polling'],
reconnection: true,
forceNew: true,
autoConnect: true,
});

listenToEvent(websocket);
websocket
.on('connect', () => websocketStore.connected.set(true))
.on('disconnect', () => websocketStore.connected.set(false))
.on('on_upload_success', (data) => websocketStore.onUploadSuccess.set(JSON.parse(data) as AssetResponseDto))
.on('on_asset_delete', (data) => websocketStore.onAssetDelete.set(JSON.parse(data) as string))
.on('on_person_thumbnail', (data) => {
console.log(data);
websocketStore.onPersonThumbnail.set(JSON.parse(data) as PersonFace);
})
.on('on_server_version', (data) => websocketStore.serverVersion.set(JSON.parse(data) as ServerVersionResponseDto))
.on('error', (e) => console.log('Websocket Error', e));

return () => websocket?.close();
} catch (e) {
console.log('Cannot connect to websocket ', e);
}
};

const listenToEvent = async (socket: Socket) => {
socket.on('on_upload_success', (data) => websocketStore.onUploadSuccess.set(JSON.parse(data) as AssetResponseDto));
socket.on('on_asset_delete', (data) => websocketStore.onAssetDelete.set(JSON.parse(data) as string));
socket.on('error', (e) => console.log('Websocket Error', e));
};

export const closeWebsocketConnection = () => {
websocket?.close();
};

0 comments on commit 80cbf53

Please sign in to comment.