mirror of
https://github.com/immich-app/immich.git
synced 2025-01-28 06:32:44 +01:00
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
This commit is contained in:
parent
2ff25b49f4
commit
be2794a372
7 changed files with 35 additions and 66 deletions
mobile
ios/fastlane
lib
modules/home
shared
server/src/api-v1/asset
|
@ -21,9 +21,9 @@ platform :ios do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.5.0"
|
version_number: "1.5.0"
|
||||||
)
|
)
|
||||||
increment_build_number({
|
increment_build_number(
|
||||||
build_number: 0
|
build_number: latest_testflight_build_number + 1,
|
||||||
})
|
)
|
||||||
build_app(scheme: "Runner",
|
build_app(scheme: "Runner",
|
||||||
workspace: "Runner.xcworkspace",
|
workspace: "Runner.xcworkspace",
|
||||||
xcargs: "-allowProvisioningUpdates")
|
xcargs: "-allowProvisioningUpdates")
|
||||||
|
|
|
@ -121,8 +121,12 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.backup_rounded)),
|
child: const Icon(Icons.backup_rounded)),
|
||||||
tooltip: 'Backup Controller',
|
tooltip: 'Backup Controller',
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
AutoRouter.of(context).push(const BackupControllerRoute());
|
var onPop = await AutoRouter.of(context).push(const BackupControllerRoute());
|
||||||
|
|
||||||
|
if (onPop != null && onPop == true) {
|
||||||
|
onPopBack!();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_backupState.backupProgress == BackUpProgressEnum.inProgress
|
_backupState.backupProgress == BackUpProgressEnum.inProgress
|
||||||
|
|
|
@ -33,6 +33,10 @@ class HomePage extends HookConsumerWidget {
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
void reloadAllAsset() {
|
||||||
|
ref.read(assetProvider.notifier).getAllAsset();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildBody() {
|
Widget _buildBody() {
|
||||||
if (assetGroupByDateTime.isNotEmpty) {
|
if (assetGroupByDateTime.isNotEmpty) {
|
||||||
int? lastMonth;
|
int? lastMonth;
|
||||||
|
@ -86,7 +90,9 @@ class HomePage extends HookConsumerWidget {
|
||||||
child: null,
|
child: null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const ImmichSliverAppBar(),
|
: ImmichSliverAppBar(
|
||||||
|
onPopBack: reloadAllAsset,
|
||||||
|
),
|
||||||
duration: const Duration(milliseconds: 350),
|
duration: const Duration(milliseconds: 350),
|
||||||
),
|
),
|
||||||
..._imageGridGroup
|
..._imageGridGroup
|
||||||
|
|
|
@ -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) {
|
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
|
||||||
|
|
|
@ -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/shared/models/backup_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.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/backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||||
|
|
||||||
class BackupControllerPage extends HookConsumerWidget {
|
class BackupControllerPage extends HookConsumerWidget {
|
||||||
|
@ -23,6 +24,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||||
ref.read(backupProvider.notifier).getBackupInfo();
|
ref.read(backupProvider.notifier).getBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success');
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
||||||
AutoRouter.of(context).pop(true);
|
AutoRouter.of(context).pop(true);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded)),
|
icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||||
|
|
|
@ -115,18 +115,8 @@ export class AssetController {
|
||||||
return this.assetService.searchAsset(authUser, searchAssetDto);
|
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('/')
|
@Get('/')
|
||||||
async getAllAssetsNoPagination(@GetAuthUser() authUser: AuthUserDto) {
|
async getAllAssets(@GetAuthUser() authUser: AuthUserDto) {
|
||||||
return await this.assetService.getAllAssetsNoPagination(authUser);
|
return await this.assetService.getAllAssetsNoPagination(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +127,7 @@ export class AssetController {
|
||||||
|
|
||||||
@Get('/assetById/:assetId')
|
@Get('/assetById/:assetId')
|
||||||
async getAssetById(@GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId) {
|
async getAssetById(@GetAuthUser() authUser: AuthUserDto, @Param('assetId') assetId) {
|
||||||
return this.assetService.getAssetById(authUser, assetId);
|
return await this.assetService.getAssetById(authUser, assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/')
|
@Delete('/')
|
||||||
|
|
|
@ -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> {
|
public async findOne(authUser: AuthUserDto, deviceId: string, assetId: string): Promise<AssetEntity> {
|
||||||
const rows = await this.assetRepository.query(
|
const rows = await this.assetRepository.query(
|
||||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."userId" = $2 AND a."deviceId" = $3',
|
'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;
|
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) {
|
public async getAssetById(authUser: AuthUserDto, assetId: string) {
|
||||||
return await this.assetRepository.findOne({
|
return await this.assetRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|
Loading…
Reference in a new issue