From be2794a3726bbd8d289106223efc1addd86c2a9c Mon Sep 17 00:00:00 2001
From: Alex <alex.tran1502@gmail.com>
Date: Sun, 3 Apr 2022 12:31:45 -0500
Subject: [PATCH] Optimization/fix slow backup when asset list is long. (#104)

* Handle pause/restart listening to event on_upload_success and reload asset list after navigating back from BackupControllerPage
* Remove unused api endpoint
---
 mobile/ios/fastlane/Fastfile                  |  6 +--
 .../modules/home/ui/immich_sliver_appbar.dart |  8 +++-
 mobile/lib/modules/home/views/home_page.dart  |  8 +++-
 .../shared/providers/websocket.provider.dart  | 14 ++++++
 .../shared/views/backup_controller_page.dart  |  3 ++
 server/src/api-v1/asset/asset.controller.ts   | 14 +-----
 server/src/api-v1/asset/asset.service.ts      | 48 -------------------
 7 files changed, 35 insertions(+), 66 deletions(-)

diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile
index 8b62fd6f0f..fc4091e3c2 100644
--- a/mobile/ios/fastlane/Fastfile
+++ b/mobile/ios/fastlane/Fastfile
@@ -21,9 +21,9 @@ platform :ios do
     increment_version_number(
       version_number: "1.5.0"
     )
-    increment_build_number({
-      build_number: 0
-    })
+    increment_build_number(
+      build_number: latest_testflight_build_number + 1,
+    )
     build_app(scheme: "Runner",
               workspace: "Runner.xcworkspace",
               xcargs: "-allowProvisioningUpdates")
diff --git a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
index 72d95b854c..185fb08b07 100644
--- a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
+++ b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
@@ -121,8 +121,12 @@ class ImmichSliverAppBar extends ConsumerWidget {
                       ),
                       child: const Icon(Icons.backup_rounded)),
               tooltip: 'Backup Controller',
-              onPressed: () {
-                AutoRouter.of(context).push(const BackupControllerRoute());
+              onPressed: () async {
+                var onPop = await AutoRouter.of(context).push(const BackupControllerRoute());
+
+                if (onPop != null && onPop == true) {
+                  onPopBack!();
+                }
               },
             ),
             _backupState.backupProgress == BackUpProgressEnum.inProgress
diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart
index 792b286572..c96c578c9a 100644
--- a/mobile/lib/modules/home/views/home_page.dart
+++ b/mobile/lib/modules/home/views/home_page.dart
@@ -33,6 +33,10 @@ class HomePage extends HookConsumerWidget {
       return null;
     }, []);
 
+    void reloadAllAsset() {
+      ref.read(assetProvider.notifier).getAllAsset();
+    }
+
     Widget _buildBody() {
       if (assetGroupByDateTime.isNotEmpty) {
         int? lastMonth;
@@ -86,7 +90,9 @@ class HomePage extends HookConsumerWidget {
                               child: null,
                             ),
                           )
-                        : const ImmichSliverAppBar(),
+                        : ImmichSliverAppBar(
+                            onPopBack: reloadAllAsset,
+                          ),
                     duration: const Duration(milliseconds: 350),
                   ),
                   ..._imageGridGroup
diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart
index 3a9c0190ec..23565feb33 100644
--- a/mobile/lib/shared/providers/websocket.provider.dart
+++ b/mobile/lib/shared/providers/websocket.provider.dart
@@ -106,6 +106,20 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
       }
     }
   }
+
+  stopListenToEvent(String eventName) {
+    debugPrint("[Websocket] Stop listening to event $eventName");
+    state.socket?.off(eventName);
+  }
+
+  listenUploadEvent() {
+    debugPrint("[Websocket] Start listening to event on_upload_success");
+    state.socket?.on('on_upload_success', (data) {
+      var jsonString = jsonDecode(data.toString());
+      ImmichAsset newAsset = ImmichAsset.fromMap(jsonString);
+      ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
+    });
+  }
 }
 
 final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
diff --git a/mobile/lib/shared/views/backup_controller_page.dart b/mobile/lib/shared/views/backup_controller_page.dart
index 58b56dad66..3fd1399a10 100644
--- a/mobile/lib/shared/views/backup_controller_page.dart
+++ b/mobile/lib/shared/views/backup_controller_page.dart
@@ -6,6 +6,7 @@ import 'package:immich_mobile/modules/login/models/authentication_state.model.da
 import 'package:immich_mobile/shared/models/backup_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';
 import 'package:percent_indicator/linear_percent_indicator.dart';
 
 class BackupControllerPage extends HookConsumerWidget {
@@ -23,6 +24,7 @@ class BackupControllerPage extends HookConsumerWidget {
         ref.read(backupProvider.notifier).getBackupInfo();
       }
 
+      ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success');
       return null;
     }, []);
 
@@ -107,6 +109,7 @@ class BackupControllerPage extends HookConsumerWidget {
         ),
         leading: IconButton(
             onPressed: () {
+              ref.watch(websocketProvider.notifier).listenUploadEvent();
               AutoRouter.of(context).pop(true);
             },
             icon: const Icon(Icons.arrow_back_ios_rounded)),
diff --git a/server/src/api-v1/asset/asset.controller.ts b/server/src/api-v1/asset/asset.controller.ts
index 5bf9fd6764..6e7f9b0ad0 100644
--- a/server/src/api-v1/asset/asset.controller.ts
+++ b/server/src/api-v1/asset/asset.controller.ts
@@ -115,18 +115,8 @@ export class AssetController {
     return this.assetService.searchAsset(authUser, searchAssetDto);
   }
 
-  @Get('/new')
-  async getNewAssets(@GetAuthUser() authUser: AuthUserDto, @Query(ValidationPipe) query: GetNewAssetQueryDto) {
-    return await this.assetService.getNewAssets(authUser, query.latestDate);
-  }
-
-  @Get('/all')
-  async getAllAssets(@GetAuthUser() authUser: AuthUserDto, @Query(ValidationPipe) query: GetAllAssetQueryDto) {
-    return await this.assetService.getAllAssets(authUser, query);
-  }
-
   @Get('/')
-  async getAllAssetsNoPagination(@GetAuthUser() authUser: AuthUserDto) {
+  async getAllAssets(@GetAuthUser() authUser: AuthUserDto) {
     return await this.assetService.getAllAssetsNoPagination(authUser);
   }
 
@@ -137,7 +127,7 @@ export class AssetController {
 
   @Get('/assetById/:assetId')
   async getAssetById(@GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId) {
-    return this.assetService.getAssetById(authUser, assetId);
+    return await this.assetService.getAssetById(authUser, assetId);
   }
 
   @Delete('/')
diff --git a/server/src/api-v1/asset/asset.service.ts b/server/src/api-v1/asset/asset.service.ts
index a217681366..40d8664e02 100644
--- a/server/src/api-v1/asset/asset.service.ts
+++ b/server/src/api-v1/asset/asset.service.ts
@@ -76,42 +76,6 @@ export class AssetService {
     }
   }
 
-  public async getAllAssets(authUser: AuthUserDto, query: GetAllAssetQueryDto): Promise<GetAllAssetReponseDto> {
-    try {
-      const assets = await this.assetRepository
-        .createQueryBuilder('a')
-        .where('a."userId" = :userId', { userId: authUser.id })
-        .andWhere('a."createdAt" < :lastQueryCreatedAt', {
-          lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(),
-        })
-        .orderBy('a."createdAt"::date', 'DESC')
-        .take(5000)
-        .getMany();
-
-      if (assets.length > 0) {
-        const data = _.groupBy(assets, (a) => new Date(a.createdAt).toISOString().slice(0, 10));
-        const formattedData = [];
-        Object.keys(data).forEach((v) => formattedData.push({ date: v, assets: data[v] }));
-
-        const response = new GetAllAssetReponseDto();
-        response.count = assets.length;
-        response.data = formattedData;
-        response.nextPageKey = assets[assets.length - 1].createdAt;
-
-        return response;
-      } else {
-        const response = new GetAllAssetReponseDto();
-        response.count = 0;
-        response.data = [];
-        response.nextPageKey = 'null';
-
-        return response;
-      }
-    } catch (e) {
-      Logger.error(e, 'getAllAssets');
-    }
-  }
-
   public async findOne(authUser: AuthUserDto, deviceId: string, assetId: string): Promise<AssetEntity> {
     const rows = await this.assetRepository.query(
       'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."userId" = $2 AND a."deviceId" = $3',
@@ -125,18 +89,6 @@ export class AssetService {
     return rows[0] as AssetEntity;
   }
 
-  public async getNewAssets(authUser: AuthUserDto, latestDate: string) {
-    return await this.assetRepository.find({
-      where: {
-        userId: authUser.id,
-        createdAt: MoreThan(latestDate),
-      },
-      order: {
-        createdAt: 'ASC', // ASC order to add existed asset the latest group first before creating a new date group.
-      },
-    });
-  }
-
   public async getAssetById(authUser: AuthUserDto, assetId: string) {
     return await this.assetRepository.findOne({
       where: {