mirror of
https://github.com/immich-app/immich.git
synced 2025-06-23 15:30:51 -04:00
fix: filename interpretation
This commit is contained in:
parent
3ccde454b1
commit
12a472ebbe
@ -156,7 +156,21 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _configureFileDownloaderNotifications() {
|
void _configureFileDownloaderNotifications() {
|
||||||
FileDownloader().configureNotification(
|
FileDownloader().configureNotificationForGroup(
|
||||||
|
downloadGroupImage,
|
||||||
|
running: TaskNotification(
|
||||||
|
'downloading_media'.tr(),
|
||||||
|
'${'file_name'.tr()}: {filename}',
|
||||||
|
),
|
||||||
|
complete: TaskNotification(
|
||||||
|
'download_finished'.tr(),
|
||||||
|
'${'file_name'.tr()}: {filename}',
|
||||||
|
),
|
||||||
|
progressBar: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
FileDownloader().configureNotificationForGroup(
|
||||||
|
downloadGroupVideo,
|
||||||
running: TaskNotification(
|
running: TaskNotification(
|
||||||
'downloading_media'.tr(),
|
'downloading_media'.tr(),
|
||||||
'${'file_name'.tr()}: {filename}',
|
'${'file_name'.tr()}: {filename}',
|
||||||
|
@ -197,7 +197,7 @@ class ExpBackupPage extends HookConsumerWidget {
|
|||||||
const RemainderCard(),
|
const RemainderCard(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
buildControlButtons(),
|
buildControlButtons(),
|
||||||
const CurrentUploadingAssetInfoBox(),
|
// const CurrentUploadingAssetInfoBox(),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
const BackupAlbumSelectionCard(),
|
const BackupAlbumSelectionCard(),
|
||||||
|
@ -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(),
|
||||||
@ -152,7 +251,6 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> cancel() async {
|
Future<void> cancel() async {
|
||||||
await _uploadService.cancel();
|
await _backupService.cancel();
|
||||||
debugPrint("Cancel uploads");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ class ExpBackupService {
|
|||||||
final IBackupRepository _backupRepository;
|
final IBackupRepository _backupRepository;
|
||||||
final IStorageRepository _storageRepository;
|
final IStorageRepository _storageRepository;
|
||||||
final UploadService _uploadService;
|
final UploadService _uploadService;
|
||||||
|
bool shouldCancel = false;
|
||||||
|
|
||||||
Future<int> getTotalCount() {
|
Future<int> getTotalCount() {
|
||||||
return _backupRepository.getTotalCount();
|
return _backupRepository.getTotalCount();
|
||||||
@ -40,6 +41,8 @@ class ExpBackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> backup() async {
|
Future<void> backup() async {
|
||||||
|
shouldCancel = false;
|
||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates();
|
final candidates = await _backupRepository.getCandidates();
|
||||||
if (candidates.isEmpty) {
|
if (candidates.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -47,6 +50,10 @@ class ExpBackupService {
|
|||||||
|
|
||||||
const batchSize = 5;
|
const batchSize = 5;
|
||||||
for (int i = 0; i < candidates.length; i += batchSize) {
|
for (int i = 0; i < candidates.length; i += batchSize) {
|
||||||
|
if (shouldCancel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
final batch = candidates.skip(i).take(batchSize).toList();
|
final batch = candidates.skip(i).take(batchSize).toList();
|
||||||
|
|
||||||
List<UploadTask> tasks = [];
|
List<UploadTask> tasks = [];
|
||||||
@ -57,7 +64,7 @@ class ExpBackupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.isNotEmpty) {
|
if (tasks.isNotEmpty && !shouldCancel) {
|
||||||
_uploadService.enqueueTasks(tasks);
|
_uploadService.enqueueTasks(tasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,6 +89,7 @@ class ExpBackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> cancel() async {
|
Future<void> cancel() async {
|
||||||
|
shouldCancel = true;
|
||||||
await _uploadService.cancel();
|
await _uploadService.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
||||||
|
@ -191,8 +191,6 @@ 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;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
auth: request.user || null,
|
auth: request.user || null,
|
||||||
fieldName: file.fieldname as UploadFieldName,
|
fieldName: file.fieldname as UploadFieldName,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user