Render when new asset is uploaded from websocket notification

This commit is contained in:
Alex Tran 2022-02-14 09:56:49 -06:00
parent 4883d548a7
commit 6d14b57fc9
14 changed files with 186 additions and 27 deletions

View file

@ -2,4 +2,7 @@ dev:
docker-compose -f ./server/docker-compose.yml up
dev-update:
docker-compose -f ./server/docker-compose.yml up --build -V
docker-compose -f ./server/docker-compose.yml up --build -V
dev-scale:
docker-compose -f ./server/docker-compose.yml up --build -V --scale immich_server=3 --remove-orphans

View file

@ -18,7 +18,6 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
List<ImmichAsset>? allAssets = await _assetService.getAllAsset();
if (allAssets != null) {
allAssets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
state = allAssets;
}
}
@ -27,6 +26,10 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
state = [];
}
onNewAssetUploaded(ImmichAsset newAsset) {
state = [...state, newAsset];
}
deleteAssets(Set<ImmichAsset> deleteAssets) async {
var deviceInfo = await _deviceInfoService.getDeviceInfo();
var deviceId = deviceInfo["deviceId"];
@ -59,14 +62,13 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
}
}
final currentLocalPageProvider = StateProvider<int>((ref) => 0);
final assetProvider = StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) {
return AssetNotifier(ref);
});
final assetGroupByDateTimeProvider = StateProvider((ref) {
var assetGroup = ref.watch(assetProvider);
var assets = ref.watch(assetProvider);
return assetGroup.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
});

View file

@ -5,6 +5,7 @@ import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/providers/backup.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
class ProfileDrawer extends ConsumerWidget {
const ProfileDrawer({Key? key}) : super(key: key);
@ -58,6 +59,7 @@ class ProfileDrawer extends ConsumerWidget {
bool res = await ref.read(authenticationProvider.notifier).logout();
if (res) {
ref.watch(websocketProvider.notifier).disconnect();
ref.watch(backupProvider.notifier).cancelBackup();
ref.watch(assetProvider.notifier).clearAllAsset();
AutoRouter.of(context).popUntilRoot();

View file

@ -32,7 +32,7 @@ class HomePage extends HookConsumerWidget {
}, []);
onPopBackFromBackupPage() {
ref.read(assetProvider.notifier).getAllAsset();
// ref.read(assetProvider.notifier).getAllAsset();
}
Widget _buildBody() {

View file

@ -15,7 +15,7 @@ class LoginForm extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final usernameController = useTextEditingController(text: 'testuser@email.com');
final passwordController = useTextEditingController(text: 'password');
final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216:2283');
final serverEndpointController = useTextEditingController(text: 'http://192.168.1.103:2283');
return Center(
child: ConstrainedBox(

View file

@ -1,6 +1,10 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:socket_io_client/socket_io_client.dart';
import 'package:immich_mobile/constants/hive_box.dart';
@ -54,7 +58,7 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
debugPrint("Attempting to connect to ws");
// Configure socket transports must be sepecified
Socket socket = io(
'http://192.168.1.216:2283',
'http://192.168.1.103:2283',
OptionBuilder()
.setTransports(['websocket'])
.enableReconnection()
@ -80,10 +84,11 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
state = WebscoketState(isConnected: false, socket: null);
});
socket.on(
'on_upload_success',
(data) => print("on new asset upload success $data"),
);
socket.on('on_upload_success', (data) {
var jsonString = jsonDecode(data.toString());
ImmichAsset newAsset = ImmichAsset.fromMap(jsonString);
ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
});
} catch (e) {
debugPrint("Catch Webcoket Error - ${e.toString()}");
}

View file

@ -2,16 +2,13 @@ version: '3.8'
services:
server:
container_name: immich_server
immich_server:
image: immich-server-dev:1.0.0
build:
context: .
target: development
dockerfile: ./Dockerfile
command: npm run start:dev
ports:
- "3000:3000"
expose:
- "3000"
volumes:
@ -62,7 +59,7 @@ services:
networks:
- immich_network
depends_on:
- server
- immich_server
networks:
immich_network:

116
server/package-lock.json generated
View file

@ -21,6 +21,7 @@
"@nestjs/platform-socket.io": "^8.2.6",
"@nestjs/typeorm": "^8.0.3",
"@nestjs/websockets": "^8.2.6",
"@socket.io/redis-adapter": "^7.1.0",
"bcrypt": "^5.0.1",
"bull": "^4.4.0",
"class-transformer": "^0.5.1",
@ -37,6 +38,7 @@
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"sharp": "0.28",
"socket.io-redis": "^6.1.1",
"systeminformation": "^5.11.0",
"typeorm": "^0.2.41"
},
@ -1820,6 +1822,20 @@
"node": ">= 0.6.0"
}
},
"node_modules/@socket.io/redis-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.1.0.tgz",
"integrity": "sha512-vbsNJKUQgtVHcOqNL2ac8kSemTVNKHRzYPldqQJt0eFKvlAtAviuAMzBP0WmOp5OoRLQMjhVsVvgMzzMsVsK5g==",
"dependencies": {
"debug": "~4.3.1",
"notepack.io": "~2.2.0",
"socket.io-adapter": "~2.3.0",
"uid2": "0.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@sqltools/formatter": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz",
@ -7580,6 +7596,11 @@
"node": ">=0.10.0"
}
},
"node_modules/notepack.io": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz",
"integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw=="
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -8403,6 +8424,24 @@
"node": ">= 0.10"
}
},
"node_modules/redis": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"dependencies": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-redis"
}
},
"node_modules/redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
@ -8958,6 +8997,27 @@
"node": ">=10.0.0"
}
},
"node_modules/socket.io-redis": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/socket.io-redis/-/socket.io-redis-6.1.1.tgz",
"integrity": "sha512-jeaXe3TGKC20GMSlPHEdwTUIWUpay/L7m5+S9TQcOf22p9Llx44/RkpJV08+buXTZ8E+aivOotj2RdeFJJWJJQ==",
"deprecated": "This package has been renamed to '@socket.io/redis-adapter', please see the migration guide here: https://socket.io/docs/v4/redis-adapter/#migrating-from-socketio-redis",
"dependencies": {
"debug": "~4.3.1",
"notepack.io": "~2.2.0",
"redis": "^3.0.0",
"socket.io-adapter": "~2.2.0",
"uid2": "0.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-redis/node_modules/socket.io-adapter": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz",
"integrity": "sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg=="
},
"node_modules/sonic-boom": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz",
@ -9995,6 +10055,11 @@
"node": ">=4.2.0"
}
},
"node_modules/uid2": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
"integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@ -11756,6 +11821,17 @@
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ=="
},
"@socket.io/redis-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-7.1.0.tgz",
"integrity": "sha512-vbsNJKUQgtVHcOqNL2ac8kSemTVNKHRzYPldqQJt0eFKvlAtAviuAMzBP0WmOp5OoRLQMjhVsVvgMzzMsVsK5g==",
"requires": {
"debug": "~4.3.1",
"notepack.io": "~2.2.0",
"socket.io-adapter": "~2.3.0",
"uid2": "0.0.3"
}
},
"@sqltools/formatter": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz",
@ -16334,6 +16410,11 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"notepack.io": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz",
"integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -16938,6 +17019,17 @@
"resolve": "^1.1.6"
}
},
"redis": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"requires": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
}
},
"redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
@ -17340,6 +17432,25 @@
"debug": "~4.3.1"
}
},
"socket.io-redis": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/socket.io-redis/-/socket.io-redis-6.1.1.tgz",
"integrity": "sha512-jeaXe3TGKC20GMSlPHEdwTUIWUpay/L7m5+S9TQcOf22p9Llx44/RkpJV08+buXTZ8E+aivOotj2RdeFJJWJJQ==",
"requires": {
"debug": "~4.3.1",
"notepack.io": "~2.2.0",
"redis": "^3.0.0",
"socket.io-adapter": "~2.2.0",
"uid2": "0.0.3"
},
"dependencies": {
"socket.io-adapter": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz",
"integrity": "sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg=="
}
}
},
"sonic-boom": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz",
@ -18022,6 +18133,11 @@
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"dev": true
},
"uid2": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
"integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",

View file

@ -33,6 +33,7 @@
"@nestjs/platform-socket.io": "^8.2.6",
"@nestjs/typeorm": "^8.0.3",
"@nestjs/websockets": "^8.2.6",
"@socket.io/redis-adapter": "^7.1.0",
"bcrypt": "^5.0.1",
"bull": "^4.4.0",
"class-transformer": "^0.5.1",

View file

@ -4,9 +4,11 @@ map $http_upgrade $connection_upgrade {
'' close;
}
server {
# events {
# worker_connections 1000;
# }
server {
client_max_body_size 50000M;

View file

@ -21,7 +21,7 @@ export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisco
handleDisconnect(client: Socket) {
client.leave(client.nsp.name);
console.log('Client left room ', client.rooms);
Logger.log(`Client ${client.id} disconnected`);
}
async handleConnection(client: Socket, ...args: any[]) {

View file

@ -1,11 +1,15 @@
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.set('trust proxy');
app.useWebSocketAdapter(new RedisIoAdapter(app));
await app.listen(3000);
}
bootstrap();

View file

@ -0,0 +1,15 @@
import { IoAdapter } from '@nestjs/platform-socket.io';
import { RedisClient, createClient } from 'redis';
import { ServerOptions } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
const pubClient = createClient({ url: 'redis://immich_redis:6379' });
const subClient = pubClient.duplicate();
export class RedisIoAdapter extends IoAdapter {
createIOServer(port: number, options?: ServerOptions): any {
const server = super.createIOServer(port, options);
server.adapter(createAdapter(pubClient, subClient));
return server;
}
}

View file

@ -57,7 +57,12 @@ export class ImageOptimizeProcessor {
return;
}
await this.assetRepository.update(savedAsset, { resizePath: desitnation });
const res = await this.assetRepository.update(savedAsset, { resizePath: desitnation });
if (res.affected) {
this.wsCommunicateionGateway.server
.to(savedAsset.userId)
.emit('on_upload_success', JSON.stringify(savedAsset));
}
});
} else {
sharp(data)
@ -68,10 +73,12 @@ export class ImageOptimizeProcessor {
return;
}
await this.assetRepository.update(savedAsset, { resizePath: resizePath });
this.wsCommunicateionGateway.server
.to(savedAsset.userId)
.emit('on_upload_success', { assetId: savedAsset.id });
const res = await this.assetRepository.update(savedAsset, { resizePath: resizePath });
if (res.affected) {
this.wsCommunicateionGateway.server
.to(savedAsset.userId)
.emit('on_upload_success', JSON.stringify(savedAsset));
}
});
}
});
@ -100,7 +107,12 @@ export class ImageOptimizeProcessor {
filename: `${filename}.png`,
})
.on('end', async (a) => {
await this.assetRepository.update(savedAsset, { resizePath: `${resizeDir}/${filename}.png` });
const res = await this.assetRepository.update(savedAsset, { resizePath: `${resizeDir}/${filename}.png` });
if (res.affected) {
this.wsCommunicateionGateway.server
.to(savedAsset.userId)
.emit('on_upload_success', JSON.stringify(savedAsset));
}
});
return 'ok';