From 49079163458b80ec1954cfd3d071799f2c442c91 Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 21 May 2024 18:58:57 +0200 Subject: [PATCH] fix(web): emit updated date when pressing enter (#9640) wip uploading format wip first working version --- mobile/android/kls_database.db | Bin 0 -> 24576 bytes mobile/lib/services/backup.service.dart | 234 ++++++++++-------- mobile/pubspec.yaml | 2 + .../lib/components/elements/date-input.svelte | 5 + 4 files changed, 138 insertions(+), 103 deletions(-) create mode 100644 mobile/android/kls_database.db diff --git a/mobile/android/kls_database.db b/mobile/android/kls_database.db new file mode 100644 index 0000000000000000000000000000000000000000..2fd56488871e91dc4c2042eccbda752d400f56fe GIT binary patch literal 24576 zcmeI&L2uJA6bEp7X&WIiio_uYE=J4f7L=x2l1}27 zeG|SNV&8xhZb~L4l&Tjl;cqpH6T8-XKl}2eoWCFViD2_(wBQNrk;jBm@|rP1h$63s zyz*9)o%NMBns;{G74l;K<4{hhKm1ObH_hjdJ|2FPT{H+l00Izz00bZa0SG_<0{=`P zeN|UA%cAM$giiy}U-`l8+>89H#0YpCk9l%waPLx>VG@1aJ=!oFQ+G|~>ivPqcF$!k zf5t4^HP1|kjU8*KI}`TKoG|^u9a*+qX=vJRhkfA@`=C3EbuMSZh2KNgt%qGzHxcW#g!k3*YuJ zP$YcD6P}UZhgW!8+nz#U&c0drp%<)XBHn}=v;Jr_Fm?MLxx=RG^Dvj0-iRpnmm%w0 zXL8#%TQELoXh%nMlI0Lz2eM%P6pQPX2t84HR0>g-23sLth>rYcJ5{Nwb-Ogxvo+sH ziYbppo?Q9)ps;FrbYF!$wJfjvc&%%5+Wbl64-EnkfB*y_009U<00Izz00bZafyxQg zs6wc+ssHat^R99UA|VJs00Izz00bZa0SG_<0uX?}|0wW;D%}U=j|bNEzx?PQ4FV8= z00bZa0SG_<0uX=z1Rwx`3JakAukhv~H3&ce0uX=z1Rwwb2tWV=5P-mM0o4CE3J`z* W1Rwwb2tWV=5P$##AOL~t3;Y6Q`xij~ literal 0 HcmV?d00001 diff --git a/mobile/lib/services/backup.service.dart b/mobile/lib/services/backup.service.dart index 8f958fff8c..9c10b6b7b1 100644 --- a/mobile/lib/services/backup.service.dart +++ b/mobile/lib/services/backup.service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:background_downloader/background_downloader.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -33,6 +34,7 @@ final backupServiceProvider = Provider( class BackupService { final httpClient = http.Client(); + final _fileDownloader = FileDownloader(); final ApiService _apiService; final Isar _db; final Logger _log = Logger("BackupService"); @@ -242,121 +244,146 @@ class BackupService { ) : assetList.toList(); + final tasks = []; for (var entity in assetsToUpload) { - File? file; - File? livePhotoFile; + final isAvailableLocally = + await entity.isLocallyAvailable(isOrigin: true); - try { - final isAvailableLocally = - await entity.isLocallyAvailable(isOrigin: true); - - // Handle getting files from iCloud - if (!isAvailableLocally && Platform.isIOS) { - // Skip iCloud assets if the user has disabled this feature - if (isIgnoreIcloudAssets) { - continue; - } - - setCurrentUploadAssetCb( - CurrentUploadAsset( - id: entity.id, - fileCreatedAt: entity.createDateTime.year == 1970 - ? entity.modifiedDateTime - : entity.createDateTime, - fileName: await entity.titleAsync, - fileType: _getAssetType(entity.type), - iCloudAsset: true, - ), - ); - - file = await entity.loadFile(progressHandler: pmProgressHandler); - livePhotoFile = await entity.loadFile( - withSubtype: true, - progressHandler: pmProgressHandler, - ); - } else { - if (entity.type == AssetType.video) { - file = await entity.originFile; - } else { - file = await entity.originFile.timeout(const Duration(seconds: 5)); - if (entity.isLivePhoto) { - livePhotoFile = await entity.originFileWithSubtype - .timeout(const Duration(seconds: 5)); - } - } + // Handle getting files from iCloud + if (!isAvailableLocally && Platform.isIOS) { + // Skip iCloud assets if the user has disabled this feature + if (isIgnoreIcloudAssets) { + continue; } - if (file != null) { - String originalFileName = await entity.titleAsync; - var fileStream = file.openRead(); - var assetRawUploadData = http.MultipartFile( - "assetData", - fileStream, - file.lengthSync(), - filename: originalFileName, - ); + setCurrentUploadAssetCb( + CurrentUploadAsset( + id: entity.id, + fileCreatedAt: entity.createDateTime.year == 1970 + ? entity.modifiedDateTime + : entity.createDateTime, + fileName: await entity.titleAsync, + fileType: _getAssetType(entity.type), + iCloudAsset: true, + ), + ); + } - var req = MultipartRequest( - 'POST', - Uri.parse('$savedEndpoint/asset/upload'), - onProgress: ((bytes, totalBytes) => - uploadProgressCb(bytes, totalBytes)), - ); - req.headers["x-immich-user-token"] = Store.get(StoreKey.accessToken); - req.headers["Transfer-Encoding"] = "chunked"; + final files = []; + // TODO: This is silly to have to load the file just to access the path + // But there doesn't seem to be any other way to do it + final fileName = (await entity.originFile)?.path; + files.add(fileName); - req.fields['deviceAssetId'] = entity.id; - req.fields['deviceId'] = deviceId; - req.fields['fileCreatedAt'] = - entity.createDateTime.toUtc().toIso8601String(); - req.fields['fileModifiedAt'] = - entity.modifiedDateTime.toUtc().toIso8601String(); - req.fields['isFavorite'] = entity.isFavorite.toString(); - req.fields['duration'] = entity.videoDuration.toString(); + if (entity.isLivePhoto) { + final livePhotoFileName = (await entity.originFileWithSubtype)?.path; + if (livePhotoFileName != null) { + files.add(livePhotoFileName); + } + } - req.files.add(assetRawUploadData); + final url = '$savedEndpoint/asset/upload'; + final headers = { + 'x-immich-user-token': Store.get(StoreKey.accessToken), + 'Transfer-Encoding': 'chunked', + }; - var fileSize = file.lengthSync(); + final fields = { + 'deviceAssetId': entity.id, + 'deviceId': deviceId, + 'fileCreatedAt': entity.createDateTime.toUtc().toIso8601String(), + 'fileModifiedAt': entity.modifiedDateTime.toUtc().toIso8601String(), + 'isFavorite': entity.isFavorite.toString(), + 'duration': entity.videoDuration.toString(), + }; - if (entity.isLivePhoto) { - if (livePhotoFile != null) { - final livePhotoTitle = p.setExtension( - originalFileName, - p.extension(livePhotoFile.path), - ); - final fileStream = livePhotoFile.openRead(); - final livePhotoRawUploadData = http.MultipartFile( - "livePhotoData", - fileStream, - livePhotoFile.lengthSync(), - filename: livePhotoTitle, - ); - req.files.add(livePhotoRawUploadData); - fileSize += livePhotoFile.lengthSync(); - } else { - _log.warning( - "Failed to obtain motion part of the livePhoto - $originalFileName", - ); - } - } + if (files.length == 1) { + final String file = files.first; + final split = file.split('/'); + final name = split.last; + final directory = split.take(split.length - 1).join('/'); - setCurrentUploadAssetCb( - CurrentUploadAsset( - id: entity.id, - fileCreatedAt: entity.createDateTime.year == 1970 - ? entity.modifiedDateTime - : entity.createDateTime, - fileName: originalFileName, - fileType: _getAssetType(entity.type), - fileSize: fileSize, - iCloudAsset: false, - ), - ); + final task = UploadTask( + url: url, + group: 'backup', + fileField: 'assetData', + taskId: entity.id, + fields: fields, + headers: headers, + updates: Updates.statusAndProgress, + retries: 0, + httpRequestMethod: 'POST', + displayName: 'Immich', + filename: name, + directory: directory, + baseDirectory: BaseDirectory.root, + ); + tasks.add(task); + } else { + final task = MultiUploadTask( + url: url, + files: files, + headers: headers, + fields: fields, + updates: Updates.statusAndProgress, + group: 'backup', + taskId: entity.id, + retries: 0, + displayName: 'Immich', + httpRequestMethod: 'POST', + baseDirectory: BaseDirectory.root, + ); - var response = - await httpClient.send(req, cancellationToken: cancelToken); + print('created task $task for files $files'); - if (response.statusCode == 200) { + tasks.add(task); + } + } + + final permission = await _fileDownloader.permissions + .status(PermissionType.androidSharedStorage); + print('has permission $permission'); + + if (tasks.length == 1) { + final result = await _fileDownloader.upload( + tasks.first, + onProgress: (percent) => print('${percent * 100} done'), + onStatus: (status) => print('status $status'), + onElapsedTime: (t) => print('time is $t'), + elapsedTimeInterval: const Duration(seconds: 1), + ); + + print('$result is done with ${result.status}'); + print('result ${result.responseBody}'); + print('result ${result.responseHeaders}'); + } else { + final result = await _fileDownloader.uploadBatch( + tasks, + batchProgressCallback: (succeeded, failed) => + print('$succeeded succeeded, $failed failed'), + taskStatusCallback: (status) => print('status $status'), + taskProgressCallback: (update) => print('update $update'), + onElapsedTime: (t) => print('time is $t'), + elapsedTimeInterval: const Duration(seconds: 1), + ); + + print( + '$result is done with ${result.succeeded.length} succeeded and ${result.failed.length} failed', + ); + + for (final task in result.succeeded) { + final r = result.results[task]; + print('successful task $task with result $r'); + } + + for (final task in result.failed) { + final r = result.results[task]; + print('failed task $task with result $r'); + } + } + + /* + if (result.status == 200) { // asset is a duplicate (already exists on the server) duplicatedAssetIds.add(entity.id); uploadSuccessCb(entity.id, deviceId, true); @@ -409,6 +436,7 @@ class BackupService { } } } + */ if (duplicatedAssetIds.isNotEmpty) { await _saveDuplicatedAssetIds(duplicatedAssetIds); } diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index ed6afd7ff7..b3340c09b1 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: octo_image: ^2.0.0 thumbhash: 0.1.0+1 async: ^2.11.0 + background_downloader: ^8.0.0 openapi: path: openapi @@ -82,6 +83,7 @@ dependency_overrides: #f url: https://github.com/Zverik/flutter-geolocator.git #f ref: floss #f path: geolocator_android + http: ^1.1.0 dev_dependencies: flutter_test: diff --git a/web/src/lib/components/elements/date-input.svelte b/web/src/lib/components/elements/date-input.svelte index 1f621b2464..ccd405c934 100644 --- a/web/src/lib/components/elements/date-input.svelte +++ b/web/src/lib/components/elements/date-input.svelte @@ -17,4 +17,9 @@ {value} on:input={(e) => (updatedValue = e.currentTarget.value)} on:blur={() => (value = updatedValue)} + on:keydown={(e) => { + if (e.key === 'Enter') { + value = updatedValue; + } + }} />