fix: filename interpretation

This commit is contained in:
Alex Tran 2025-06-11 16:39:56 -05:00 committed by Alex
parent 3ccde454b1
commit 1ba5bd7173
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
5 changed files with 150 additions and 43 deletions

View File

@ -2,35 +2,99 @@
import 'dart:convert'; import 'dart:convert';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/services/exp_backup.service.dart'; import 'package:immich_mobile/services/exp_backup.service.dart';
import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/services/upload.service.dart';
class ExpUploadStatus {
final String taskId;
final String filename;
final double progress;
ExpUploadStatus({
required this.taskId,
required this.filename,
required this.progress,
});
ExpUploadStatus copyWith({
String? taskId,
String? filename,
double? progress,
}) {
return ExpUploadStatus(
taskId: taskId ?? this.taskId,
filename: filename ?? this.filename,
progress: progress ?? this.progress,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'taskId': taskId,
'filename': filename,
'progress': progress,
};
}
factory ExpUploadStatus.fromMap(Map<String, dynamic> map) {
return ExpUploadStatus(
taskId: map['taskId'] as String,
filename: map['filename'] as String,
progress: map['progress'] as double,
);
}
String toJson() => json.encode(toMap());
factory ExpUploadStatus.fromJson(String source) =>
ExpUploadStatus.fromMap(json.decode(source) as Map<String, dynamic>);
@override
String toString() =>
'ExpUploadStatus(taskId: $taskId, filename: $filename, progress: $progress)';
@override
bool operator ==(covariant ExpUploadStatus other) {
if (identical(this, other)) return true;
return other.taskId == taskId &&
other.filename == filename &&
other.progress == progress;
}
@override
int get hashCode => taskId.hashCode ^ filename.hashCode ^ progress.hashCode;
}
class ExpBackupState { class ExpBackupState {
final int totalCount; final int totalCount;
final int backupCount; final int backupCount;
final int remainderCount; final int remainderCount;
final Map<String, ExpUploadStatus> uploadItems;
ExpBackupState({ ExpBackupState({
required this.totalCount, required this.totalCount,
required this.backupCount, required this.backupCount,
required this.remainderCount, required this.remainderCount,
required this.uploadItems,
}); });
ExpBackupState copyWith({ ExpBackupState copyWith({
int? totalCount, int? totalCount,
int? backupCount, int? backupCount,
int? remainderCount, int? remainderCount,
Map<String, ExpUploadStatus>? uploadItems,
}) { }) {
return ExpBackupState( return ExpBackupState(
totalCount: totalCount ?? this.totalCount, totalCount: totalCount ?? this.totalCount,
backupCount: backupCount ?? this.backupCount, backupCount: backupCount ?? this.backupCount,
remainderCount: remainderCount ?? this.remainderCount, remainderCount: remainderCount ?? this.remainderCount,
uploadItems: uploadItems ?? this.uploadItems,
); );
} }
@ -39,6 +103,7 @@ class ExpBackupState {
'totalCount': totalCount, 'totalCount': totalCount,
'backupCount': backupCount, 'backupCount': backupCount,
'remainderCount': remainderCount, 'remainderCount': remainderCount,
'uploadItems': uploadItems,
}; };
} }
@ -47,6 +112,14 @@ class ExpBackupState {
totalCount: map['totalCount'] as int, totalCount: map['totalCount'] as int,
backupCount: map['backupCount'] as int, backupCount: map['backupCount'] as int,
remainderCount: map['remainderCount'] as int, remainderCount: map['remainderCount'] as int,
uploadItems: Map<String, ExpUploadStatus>.from(
(map['uploadItems'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
ExpUploadStatus.fromMap(value as Map<String, dynamic>),
),
),
),
); );
} }
@ -56,21 +129,28 @@ class ExpBackupState {
ExpBackupState.fromMap(json.decode(source) as Map<String, dynamic>); ExpBackupState.fromMap(json.decode(source) as Map<String, dynamic>);
@override @override
String toString() => String toString() {
'ExpBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount)'; return 'ExpBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, uploadItems: $uploadItems)';
}
@override @override
bool operator ==(covariant ExpBackupState other) { bool operator ==(covariant ExpBackupState other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
final mapEquals = const DeepCollectionEquality().equals;
return other.totalCount == totalCount && return other.totalCount == totalCount &&
other.backupCount == backupCount && other.backupCount == backupCount &&
other.remainderCount == remainderCount; other.remainderCount == remainderCount &&
mapEquals(other.uploadItems, uploadItems);
} }
@override @override
int get hashCode => int get hashCode {
totalCount.hashCode ^ backupCount.hashCode ^ remainderCount.hashCode; return totalCount.hashCode ^
backupCount.hashCode ^
remainderCount.hashCode ^
uploadItems.hashCode;
}
} }
final expBackupProvider = final expBackupProvider =
@ -92,6 +172,7 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
totalCount: 0, totalCount: 0,
backupCount: 0, backupCount: 0,
remainderCount: 0, remainderCount: 0,
uploadItems: {},
), ),
) { ) {
{ {
@ -120,8 +201,6 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
remainderCount: state.remainderCount - 1, remainderCount: state.remainderCount - 1,
); );
// TODO: find a better place to call this.
_backgroundSyncManager.syncRemote();
break; break;
default: default:
@ -130,10 +209,30 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
} }
void _taskProgressCallback(TaskProgressUpdate update) { void _taskProgressCallback(TaskProgressUpdate update) {
debugPrint("[_taskProgressCallback] $update"); final uploadStatus = ExpUploadStatus(
taskId: update.task.taskId,
filename: update.task.displayName,
progress: update.progress,
);
state = state.copyWith(
uploadItems: {
for (final entry in state.uploadItems.entries)
if (entry.key == update.task.taskId)
entry.key: uploadStatus
else
entry.key: entry.value,
if (!state.uploadItems.containsKey(update.task.taskId))
update.task.taskId: uploadStatus,
},
);
print(update.task.taskId);
} }
Future<void> getBackupStatus() async { Future<void> getBackupStatus() async {
await _backgroundSyncManager.syncRemote();
final [totalCount, backupCount, remainderCount] = await Future.wait([ final [totalCount, backupCount, remainderCount] = await Future.wait([
_backupService.getTotalCount(), _backupService.getTotalCount(),
_backupService.getBackupCount(), _backupService.getBackupCount(),

View File

@ -108,7 +108,7 @@ class UploadService {
return UploadTask( return UploadTask(
taskId: id, taskId: id,
displayName: filename, displayName: originalFileName ?? filename,
httpRequestMethod: 'POST', httpRequestMethod: 'POST',
url: url, url: url,
headers: headers, headers: headers,

View File

@ -2,43 +2,53 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/backup/exp_backup.provider.dart';
import 'package:immich_mobile/widgets/backup/asset_info_table.dart'; import 'package:immich_mobile/widgets/backup/asset_info_table.dart';
import 'package:immich_mobile/widgets/backup/error_chip.dart'; import 'package:immich_mobile/widgets/backup/error_chip.dart';
import 'package:immich_mobile/widgets/backup/icloud_download_progress_bar.dart'; import 'package:immich_mobile/widgets/backup/icloud_download_progress_bar.dart';
import 'package:immich_mobile/widgets/backup/upload_progress_bar.dart'; import 'package:immich_mobile/widgets/backup/upload_progress_bar.dart';
import 'package:immich_mobile/widgets/backup/upload_stats.dart'; import 'package:immich_mobile/widgets/backup/upload_stats.dart';
class CurrentUploadingAssetInfoBox extends StatelessWidget { class CurrentUploadingAssetInfoBox extends ConsumerWidget {
const CurrentUploadingAssetInfoBox({super.key}); const CurrentUploadingAssetInfoBox({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
return ListTile( final uploadItems =
isThreeLine: true, ref.watch(expBackupProvider.select((state) => state.uploadItems));
leading: Icon(
Icons.image_outlined, return Column(
color: context.primaryColor, children: [
size: 30, if (uploadItems.isNotEmpty)
), Container(
title: Row( constraints: const BoxConstraints(maxHeight: 200),
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: ListView.builder(
children: [ shrinkWrap: true,
Text( itemCount: uploadItems.length,
"backup_controller_page_uploading_file_info", itemBuilder: (context, index) {
style: context.textTheme.titleSmall, final uploadItem = uploadItems.values.elementAt(index);
).tr(), return ListTile(
const BackupErrorChip(), dense: true,
], leading: CircularProgressIndicator(
), value: uploadItem.progress,
subtitle: Column( strokeWidth: 2,
children: [ ),
if (Platform.isIOS) const IcloudDownloadProgressBar(), title: Text(
const BackupUploadProgressBar(), uploadItem.filename,
const BackupUploadStats(), style: context.textTheme.bodySmall,
const BackupAssetInfoTable(), overflow: TextOverflow.ellipsis,
], ),
), trailing: Text(
'${(uploadItem.progress * 100).toInt()}%',
style: context.textTheme.bodySmall,
),
);
},
),
),
],
); );
} }
} }

View File

@ -103,10 +103,6 @@ export class FileUploadInterceptor implements NestInterceptor {
} }
private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) {
console.log(
'FileUploadInterceptor.filename called with file:',
this.assetService.getUploadFilename(asRequest(request, file)),
);
return callbackify( return callbackify(
() => this.assetService.getUploadFilename(asRequest(request, file)), () => this.assetService.getUploadFilename(asRequest(request, file)),
callback as Callback<string>, callback as Callback<string>,

View File

@ -191,7 +191,9 @@ export function mapToUploadFile(file: ImmichFile): UploadFile {
} }
export const asRequest = (request: AuthRequest, file: Express.Multer.File) => { export const asRequest = (request: AuthRequest, file: Express.Multer.File) => {
file.originalname = request.body['filename'] ?? file.filename; if (request.body['filename'] != undefined) {
file.originalname = request.body['filename'];
}
return { return {
auth: request.user || null, auth: request.user || null,