mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
fix(web): emit updated date when pressing enter (#9640)
wip uploading format wip first working version
This commit is contained in:
parent
a3489d604b
commit
4907916345
4 changed files with 138 additions and 103 deletions
BIN
mobile/android/kls_database.db
Normal file
BIN
mobile/android/kls_database.db
Normal file
Binary file not shown.
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
@ -33,6 +34,7 @@ final backupServiceProvider = Provider(
|
||||||
|
|
||||||
class BackupService {
|
class BackupService {
|
||||||
final httpClient = http.Client();
|
final httpClient = http.Client();
|
||||||
|
final _fileDownloader = FileDownloader();
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final Isar _db;
|
final Isar _db;
|
||||||
final Logger _log = Logger("BackupService");
|
final Logger _log = Logger("BackupService");
|
||||||
|
@ -242,121 +244,146 @@ class BackupService {
|
||||||
)
|
)
|
||||||
: assetList.toList();
|
: assetList.toList();
|
||||||
|
|
||||||
|
final tasks = <UploadTask>[];
|
||||||
for (var entity in assetsToUpload) {
|
for (var entity in assetsToUpload) {
|
||||||
File? file;
|
final isAvailableLocally =
|
||||||
File? livePhotoFile;
|
await entity.isLocallyAvailable(isOrigin: true);
|
||||||
|
|
||||||
try {
|
// Handle getting files from iCloud
|
||||||
final isAvailableLocally =
|
if (!isAvailableLocally && Platform.isIOS) {
|
||||||
await entity.isLocallyAvailable(isOrigin: true);
|
// Skip iCloud assets if the user has disabled this feature
|
||||||
|
if (isIgnoreIcloudAssets) {
|
||||||
// Handle getting files from iCloud
|
continue;
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file != null) {
|
setCurrentUploadAssetCb(
|
||||||
String originalFileName = await entity.titleAsync;
|
CurrentUploadAsset(
|
||||||
var fileStream = file.openRead();
|
id: entity.id,
|
||||||
var assetRawUploadData = http.MultipartFile(
|
fileCreatedAt: entity.createDateTime.year == 1970
|
||||||
"assetData",
|
? entity.modifiedDateTime
|
||||||
fileStream,
|
: entity.createDateTime,
|
||||||
file.lengthSync(),
|
fileName: await entity.titleAsync,
|
||||||
filename: originalFileName,
|
fileType: _getAssetType(entity.type),
|
||||||
);
|
iCloudAsset: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var req = MultipartRequest(
|
final files = [];
|
||||||
'POST',
|
// TODO: This is silly to have to load the file just to access the path
|
||||||
Uri.parse('$savedEndpoint/asset/upload'),
|
// But there doesn't seem to be any other way to do it
|
||||||
onProgress: ((bytes, totalBytes) =>
|
final fileName = (await entity.originFile)?.path;
|
||||||
uploadProgressCb(bytes, totalBytes)),
|
files.add(fileName);
|
||||||
);
|
|
||||||
req.headers["x-immich-user-token"] = Store.get(StoreKey.accessToken);
|
|
||||||
req.headers["Transfer-Encoding"] = "chunked";
|
|
||||||
|
|
||||||
req.fields['deviceAssetId'] = entity.id;
|
if (entity.isLivePhoto) {
|
||||||
req.fields['deviceId'] = deviceId;
|
final livePhotoFileName = (await entity.originFileWithSubtype)?.path;
|
||||||
req.fields['fileCreatedAt'] =
|
if (livePhotoFileName != null) {
|
||||||
entity.createDateTime.toUtc().toIso8601String();
|
files.add(livePhotoFileName);
|
||||||
req.fields['fileModifiedAt'] =
|
}
|
||||||
entity.modifiedDateTime.toUtc().toIso8601String();
|
}
|
||||||
req.fields['isFavorite'] = entity.isFavorite.toString();
|
|
||||||
req.fields['duration'] = entity.videoDuration.toString();
|
|
||||||
|
|
||||||
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 (files.length == 1) {
|
||||||
if (livePhotoFile != null) {
|
final String file = files.first;
|
||||||
final livePhotoTitle = p.setExtension(
|
final split = file.split('/');
|
||||||
originalFileName,
|
final name = split.last;
|
||||||
p.extension(livePhotoFile.path),
|
final directory = split.take(split.length - 1).join('/');
|
||||||
);
|
|
||||||
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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentUploadAssetCb(
|
final task = UploadTask(
|
||||||
CurrentUploadAsset(
|
url: url,
|
||||||
id: entity.id,
|
group: 'backup',
|
||||||
fileCreatedAt: entity.createDateTime.year == 1970
|
fileField: 'assetData',
|
||||||
? entity.modifiedDateTime
|
taskId: entity.id,
|
||||||
: entity.createDateTime,
|
fields: fields,
|
||||||
fileName: originalFileName,
|
headers: headers,
|
||||||
fileType: _getAssetType(entity.type),
|
updates: Updates.statusAndProgress,
|
||||||
fileSize: fileSize,
|
retries: 0,
|
||||||
iCloudAsset: false,
|
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 =
|
print('created task $task for files $files');
|
||||||
await httpClient.send(req, cancellationToken: cancelToken);
|
|
||||||
|
|
||||||
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)
|
// asset is a duplicate (already exists on the server)
|
||||||
duplicatedAssetIds.add(entity.id);
|
duplicatedAssetIds.add(entity.id);
|
||||||
uploadSuccessCb(entity.id, deviceId, true);
|
uploadSuccessCb(entity.id, deviceId, true);
|
||||||
|
@ -409,6 +436,7 @@ class BackupService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if (duplicatedAssetIds.isNotEmpty) {
|
if (duplicatedAssetIds.isNotEmpty) {
|
||||||
await _saveDuplicatedAssetIds(duplicatedAssetIds);
|
await _saveDuplicatedAssetIds(duplicatedAssetIds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ dependencies:
|
||||||
octo_image: ^2.0.0
|
octo_image: ^2.0.0
|
||||||
thumbhash: 0.1.0+1
|
thumbhash: 0.1.0+1
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
|
background_downloader: ^8.0.0
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
@ -82,6 +83,7 @@ dependency_overrides:
|
||||||
#f url: https://github.com/Zverik/flutter-geolocator.git
|
#f url: https://github.com/Zverik/flutter-geolocator.git
|
||||||
#f ref: floss
|
#f ref: floss
|
||||||
#f path: geolocator_android
|
#f path: geolocator_android
|
||||||
|
http: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -17,4 +17,9 @@
|
||||||
{value}
|
{value}
|
||||||
on:input={(e) => (updatedValue = e.currentTarget.value)}
|
on:input={(e) => (updatedValue = e.currentTarget.value)}
|
||||||
on:blur={() => (value = updatedValue)}
|
on:blur={() => (value = updatedValue)}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
value = updatedValue;
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue