mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:06:56 -04:00
dev: switch from pub cancellation_token_http to background_downloader to use URLSessionTasks on iOS for background sync
This commit is contained in:
parent
67df25cc9e
commit
7135f986b7
@ -13,6 +13,7 @@ struct BackgroundSyncAppShortcut: AppShortcutsProvider {
|
|||||||
|
|
||||||
@AppShortcutsBuilder static var appShortcuts: [AppShortcut] {
|
@AppShortcutsBuilder static var appShortcuts: [AppShortcut] {
|
||||||
AppShortcut(intent: BackgroundSyncShortcutIntent(), phrases: [
|
AppShortcut(intent: BackgroundSyncShortcutIntent(), phrases: [
|
||||||
|
// TODO: localized title
|
||||||
"Upload gallery using \(.applicationName)"], systemImageName: "square.and.arrow.up.on.square")
|
"Upload gallery using \(.applicationName)"], systemImageName: "square.and.arrow.up.on.square")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,13 @@ import SwiftUI
|
|||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
struct BackgroundSyncShortcutIntent: AppIntent {
|
struct BackgroundSyncShortcutIntent: AppIntent {
|
||||||
|
|
||||||
|
// TODO: localized title and description
|
||||||
static var title: LocalizedStringResource = "Sync gallery"
|
static var title: LocalizedStringResource = "Sync gallery"
|
||||||
|
|
||||||
|
static var openAppWhenRun: Bool = true
|
||||||
|
|
||||||
|
static var isDiscoverable: Bool = true
|
||||||
|
|
||||||
func perform() async throws -> some IntentResult {
|
func perform() async throws -> some IntentResult {
|
||||||
|
|
||||||
let backgroundWorker = BackgroundSyncWorker { _ in () }
|
let backgroundWorker = BackgroundSyncWorker { _ in () }
|
||||||
|
@ -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:cancellation_token_http/http.dart' as http;
|
import 'package:cancellation_token_http/http.dart' as http;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -339,31 +340,29 @@ class BackupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final fileStream = file.openRead();
|
final fileLength = file.lengthSync();
|
||||||
final assetRawUploadData = http.MultipartFile(
|
|
||||||
"assetData",
|
final (baseDir, dir, _) = await Task.split(file: file);
|
||||||
fileStream,
|
|
||||||
file.lengthSync(),
|
final backgroundRequest = UploadTask(
|
||||||
filename: originalFileName,
|
filename: originalFileName,
|
||||||
);
|
baseDirectory: baseDir,
|
||||||
|
directory: dir,
|
||||||
|
url: '$savedEndpoint/assets',
|
||||||
|
httpRequestMethod: 'POST',
|
||||||
|
priority: 10,
|
||||||
|
); // Priority 10 for testing purposes; maybe we could change that to a lower value later
|
||||||
|
|
||||||
final baseRequest = MultipartRequest(
|
backgroundRequest.headers.addAll(ApiService.getRequestHeaders());
|
||||||
'POST',
|
backgroundRequest.headers["Transfer-Encoding"] = "chunked";
|
||||||
Uri.parse('$savedEndpoint/assets'),
|
backgroundRequest.fields['deviceAssetId'] = asset.localId!;
|
||||||
onProgress: ((bytes, totalBytes) => onProgress(bytes, totalBytes)),
|
backgroundRequest.fields['deviceId'] = deviceId;
|
||||||
);
|
backgroundRequest.fields['fileCreatedAt'] =
|
||||||
|
|
||||||
baseRequest.headers.addAll(ApiService.getRequestHeaders());
|
|
||||||
baseRequest.headers["Transfer-Encoding"] = "chunked";
|
|
||||||
baseRequest.fields['deviceAssetId'] = asset.localId!;
|
|
||||||
baseRequest.fields['deviceId'] = deviceId;
|
|
||||||
baseRequest.fields['fileCreatedAt'] =
|
|
||||||
asset.fileCreatedAt.toUtc().toIso8601String();
|
asset.fileCreatedAt.toUtc().toIso8601String();
|
||||||
baseRequest.fields['fileModifiedAt'] =
|
backgroundRequest.fields['fileModifiedAt'] =
|
||||||
asset.fileModifiedAt.toUtc().toIso8601String();
|
asset.fileModifiedAt.toUtc().toIso8601String();
|
||||||
baseRequest.fields['isFavorite'] = asset.isFavorite.toString();
|
backgroundRequest.fields['isFavorite'] = asset.isFavorite.toString();
|
||||||
baseRequest.fields['duration'] = asset.duration.toString();
|
backgroundRequest.fields['duration'] = asset.duration.toString();
|
||||||
baseRequest.files.add(assetRawUploadData);
|
|
||||||
|
|
||||||
onCurrentAsset(
|
onCurrentAsset(
|
||||||
CurrentUploadAsset(
|
CurrentUploadAsset(
|
||||||
@ -383,29 +382,37 @@ class BackupService {
|
|||||||
livePhotoVideoId = await uploadLivePhotoVideo(
|
livePhotoVideoId = await uploadLivePhotoVideo(
|
||||||
originalFileName,
|
originalFileName,
|
||||||
livePhotoFile,
|
livePhotoFile,
|
||||||
baseRequest,
|
backgroundRequest,
|
||||||
cancelToken,
|
cancelToken,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (livePhotoVideoId != null) {
|
if (livePhotoVideoId != null) {
|
||||||
baseRequest.fields['livePhotoVideoId'] = livePhotoVideoId;
|
backgroundRequest.fields['livePhotoVideoId'] = livePhotoVideoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await httpClient.send(
|
final response = await FileDownloader().upload(
|
||||||
baseRequest,
|
backgroundRequest,
|
||||||
cancellationToken: cancelToken,
|
onProgress: (percentage) => {
|
||||||
|
// onProgress returns a double in [0.0;1.0] for percentage
|
||||||
|
if (percentage > 0)
|
||||||
|
onProgress(
|
||||||
|
(percentage * fileLength).toInt(),
|
||||||
|
fileLength,
|
||||||
|
),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final responseBody =
|
final responseBody = jsonDecode(response.responseBody ?? "{}");
|
||||||
jsonDecode(await response.stream.bytesToString());
|
|
||||||
|
|
||||||
if (![200, 201].contains(response.statusCode)) {
|
if (response.status == TaskStatus.failed ||
|
||||||
final error = responseBody;
|
![200, 201].contains(response.responseStatusCode)) {
|
||||||
final errorMessage = error['message'] ?? error['error'];
|
final error = response.exception != null
|
||||||
|
? response.exception!.description
|
||||||
|
: responseBody;
|
||||||
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Error(${error['statusCode']}) uploading ${asset.localId} | $originalFileName | Created on ${asset.fileCreatedAt} | ${error['error']}",
|
"Error(${response.responseStatusCode}) uploading ${asset.localId} | $originalFileName | Created on ${asset.fileCreatedAt} | $error",
|
||||||
);
|
);
|
||||||
|
|
||||||
onError(
|
onError(
|
||||||
@ -415,11 +422,11 @@ class BackupService {
|
|||||||
fileCreatedAt: asset.fileCreatedAt,
|
fileCreatedAt: asset.fileCreatedAt,
|
||||||
fileName: originalFileName,
|
fileName: originalFileName,
|
||||||
fileType: _getAssetType(candidate.asset.type),
|
fileType: _getAssetType(candidate.asset.type),
|
||||||
errorMessage: errorMessage,
|
errorMessage: error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (errorMessage == "Quota has been exceeded!") {
|
if (error == "Quota has been exceeded!") {
|
||||||
anyErrors = true;
|
anyErrors = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -428,7 +435,7 @@ class BackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isDuplicate = false;
|
bool isDuplicate = false;
|
||||||
if (response.statusCode == 200) {
|
if (response.responseStatusCode == 200) {
|
||||||
isDuplicate = true;
|
isDuplicate = true;
|
||||||
duplicatedAssetIds.add(asset.localId!);
|
duplicatedAssetIds.add(asset.localId!);
|
||||||
}
|
}
|
||||||
@ -478,7 +485,7 @@ class BackupService {
|
|||||||
Future<String?> uploadLivePhotoVideo(
|
Future<String?> uploadLivePhotoVideo(
|
||||||
String originalFileName,
|
String originalFileName,
|
||||||
File? livePhotoVideoFile,
|
File? livePhotoVideoFile,
|
||||||
MultipartRequest baseRequest,
|
UploadTask baseRequest,
|
||||||
http.CancellationToken cancelToken,
|
http.CancellationToken cancelToken,
|
||||||
) async {
|
) async {
|
||||||
if (livePhotoVideoFile == null) {
|
if (livePhotoVideoFile == null) {
|
||||||
@ -488,35 +495,33 @@ class BackupService {
|
|||||||
originalFileName,
|
originalFileName,
|
||||||
p.extension(livePhotoVideoFile.path),
|
p.extension(livePhotoVideoFile.path),
|
||||||
);
|
);
|
||||||
final fileStream = livePhotoVideoFile.openRead();
|
|
||||||
final livePhotoRawUploadData = http.MultipartFile(
|
final (baseDir, dir, _) = await Task.split(file: livePhotoVideoFile);
|
||||||
"assetData",
|
|
||||||
fileStream,
|
final backgroundRequest = UploadTask(
|
||||||
livePhotoVideoFile.lengthSync(),
|
|
||||||
filename: livePhotoTitle,
|
filename: livePhotoTitle,
|
||||||
);
|
baseDirectory: baseDir,
|
||||||
final livePhotoReq = MultipartRequest(
|
directory: dir,
|
||||||
baseRequest.method,
|
url: baseRequest.url,
|
||||||
baseRequest.url,
|
httpRequestMethod: baseRequest.httpRequestMethod,
|
||||||
onProgress: baseRequest.onProgress,
|
priority: baseRequest.priority,
|
||||||
)
|
)
|
||||||
..headers.addAll(baseRequest.headers)
|
..headers.addAll(baseRequest.headers)
|
||||||
..fields.addAll(baseRequest.fields);
|
..fields.addAll(baseRequest.fields);
|
||||||
|
|
||||||
livePhotoReq.files.add(livePhotoRawUploadData);
|
final response = await FileDownloader()
|
||||||
|
.upload(backgroundRequest); //TODO: onProgress callback?
|
||||||
|
|
||||||
var response = await httpClient.send(
|
var responseBody = jsonDecode(response.responseBody ?? "{}");
|
||||||
livePhotoReq,
|
|
||||||
cancellationToken: cancelToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
var responseBody = jsonDecode(await response.stream.bytesToString());
|
if (response.status == TaskStatus.failed ||
|
||||||
|
![200, 201].contains(response.responseStatusCode)) {
|
||||||
if (![200, 201].contains(response.statusCode)) {
|
final error = response.exception != null
|
||||||
var error = responseBody;
|
? response.exception!.description
|
||||||
|
: responseBody;
|
||||||
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Error(${error['statusCode']}) uploading livePhoto for assetId | $livePhotoTitle | ${error['error']}",
|
"Error(${error['statusCode']}) uploading livePhoto for assetId | $livePhotoTitle | $error",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.0.0"
|
||||||
|
background_downloader:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: background_downloader
|
||||||
|
sha256: "6a945db1a1c7727a4bc9c1d7c882cfb1a819f873b77e01d5e5dd6a3fb231cb28"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.5.5"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -744,10 +752,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.6"
|
version: "1.2.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -32,7 +32,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.0.9
|
flutter_svg: ^2.0.9
|
||||||
package_info_plus: ^8.0.1
|
package_info_plus: ^8.0.1
|
||||||
url_launcher: ^6.2.4
|
url_launcher: ^6.2.4
|
||||||
http: ^0.13.6
|
http: ^1.1.0
|
||||||
cancellation_token_http: ^2.0.0
|
cancellation_token_http: ^2.0.0
|
||||||
easy_localization: ^3.0.3
|
easy_localization: ^3.0.3
|
||||||
share_plus: ^10.0.0
|
share_plus: ^10.0.0
|
||||||
@ -56,6 +56,7 @@ dependencies:
|
|||||||
thumbhash: 0.1.0+1
|
thumbhash: 0.1.0+1
|
||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
dynamic_color: ^1.7.0 #package to apply system theme
|
dynamic_color: ^1.7.0 #package to apply system theme
|
||||||
|
background_downloader: ^8.5.5
|
||||||
|
|
||||||
#image editing packages
|
#image editing packages
|
||||||
crop_image: ^1.0.13
|
crop_image: ^1.0.13
|
||||||
|
Loading…
x
Reference in New Issue
Block a user