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