mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
upload asset button
This commit is contained in:
parent
8128876472
commit
b59c4ddc9c
6 changed files with 179 additions and 0 deletions
|
|
@ -304,6 +304,7 @@ interface NativeSyncApi {
|
||||||
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
|
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
|
||||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||||
|
fun uploadAsset(callback: (Result<Boolean>) -> Unit)
|
||||||
fun cancelHashing()
|
fun cancelHashing()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -467,6 +468,24 @@ interface NativeSyncApi {
|
||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.uploadAsset$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.uploadAsset{ result: Result<Boolean> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
val data = result.getOrNull()
|
||||||
|
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
run {
|
run {
|
||||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec)
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec)
|
||||||
if (api != null) {
|
if (api != null) {
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,7 @@ protocol NativeSyncApi {
|
||||||
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
|
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
|
||||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||||
|
func uploadAsset(completion: @escaping (Result<Bool, Error>) -> Void)
|
||||||
func cancelHashing() throws
|
func cancelHashing() throws
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,6 +520,21 @@ class NativeSyncApiSetup {
|
||||||
} else {
|
} else {
|
||||||
hashAssetsChannel.setMessageHandler(nil)
|
hashAssetsChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
|
let uploadAssetChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.uploadAsset\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
if let api = api {
|
||||||
|
uploadAssetChannel.setMessageHandler { _, reply in
|
||||||
|
api.uploadAsset { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let res):
|
||||||
|
reply(wrapResult(res))
|
||||||
|
case .failure(let error):
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uploadAssetChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
let cancelHashingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
let cancelHashingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
if let api = api {
|
if let api = api {
|
||||||
cancelHashingChannel.setMessageHandler { _, reply in
|
cancelHashingChannel.setMessageHandler { _, reply in
|
||||||
|
|
|
||||||
|
|
@ -363,4 +363,38 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||||
PHAssetResourceManager.default().cancelDataRequest(requestId)
|
PHAssetResourceManager.default().cancelDataRequest(requestId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uploadAsset(completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||||
|
let bufferSize = 200 * 1024 * 1024
|
||||||
|
var buffer = Data(count: bufferSize)
|
||||||
|
buffer.withUnsafeMutableBytes { bufferPointer in
|
||||||
|
arc4random_buf(bufferPointer.baseAddress!, bufferSize)
|
||||||
|
}
|
||||||
|
var hasher = Insecure.SHA1()
|
||||||
|
hasher.update(data: buffer)
|
||||||
|
let checksum = Data(hasher.finalize()).base64EncodedString()
|
||||||
|
let tempDirectory = FileManager.default.temporaryDirectory
|
||||||
|
|
||||||
|
let tempFileURL = tempDirectory.appendingPathComponent("buffer.tmp")
|
||||||
|
do {
|
||||||
|
try buffer.write(to: tempFileURL)
|
||||||
|
print("File saved to: \(tempFileURL.path)")
|
||||||
|
} catch {
|
||||||
|
print("Error writing file: \(error)")
|
||||||
|
return completion(Result.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = URLSessionConfiguration.background(withIdentifier: "app.mertalev.immich.upload")
|
||||||
|
let session = URLSession(configuration: config)
|
||||||
|
|
||||||
|
var request = URLRequest(url: URL(string: "https://<hardcoded-host>/api/upload")!)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue("<hardcoded-api-key>", forHTTPHeaderField: "X-Api-Key")
|
||||||
|
request.setValue("filename=\"test-image.jpg\", device-asset-id=\"rufh\", device-id=\"test\", file-created-at=\"2025-01-02T00:00:00.000Z\", file-modified-at=\"2025-01-01T00:00:00.000Z\", is-favorite, icloud-id=\"example-icloud-id\"", forHTTPHeaderField: "X-Immich-Asset-Data")
|
||||||
|
request.setValue("sha=:\(checksum):", forHTTPHeaderField: "Repr-Digest")
|
||||||
|
|
||||||
|
let task = session.uploadTask(with: request, fromFile: tempFileURL)
|
||||||
|
task.resume()
|
||||||
|
completion(Result.success(true))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.w
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
|
@ -141,6 +142,84 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||||
await stopBackup();
|
await stopBackup();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
context.primaryColor.withValues(alpha: 0.5),
|
||||||
|
context.primaryColor.withValues(alpha: 0.4),
|
||||||
|
context.primaryColor.withValues(alpha: 0.5),
|
||||||
|
],
|
||||||
|
stops: const [0.0, 0.5, 1.0],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(1.5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(18.5)),
|
||||||
|
color: context.colorScheme.surfaceContainerLow,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: context.colorScheme.surfaceContainerLow,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
||||||
|
onTap: () => ref.read(nativeSyncApiProvider).uploadAsset(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
context.primaryColor.withValues(alpha: 0.2),
|
||||||
|
context.primaryColor.withValues(alpha: 0.1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.upload, color: context.primaryColor, size: 24),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Upload Asset (for testing)",
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
switch (error) {
|
switch (error) {
|
||||||
BackupError.none => const SizedBox.shrink(),
|
BackupError.none => const SizedBox.shrink(),
|
||||||
BackupError.syncFailed => Padding(
|
BackupError.syncFailed => Padding(
|
||||||
|
|
|
||||||
28
mobile/lib/platform/native_sync_api.g.dart
generated
28
mobile/lib/platform/native_sync_api.g.dart
generated
|
|
@ -540,6 +540,34 @@ class NativeSyncApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> uploadAsset() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.uploadAsset$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as bool?)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> cancelHashing() async {
|
Future<void> cancelHashing() async {
|
||||||
final String pigeonVar_channelName =
|
final String pigeonVar_channelName =
|
||||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$pigeonVar_messageChannelSuffix';
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$pigeonVar_messageChannelSuffix';
|
||||||
|
|
|
||||||
|
|
@ -106,5 +106,8 @@ abstract class NativeSyncApi {
|
||||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
List<HashResult> hashAssets(List<String> assetIds, {bool allowNetworkAccess = false});
|
List<HashResult> hashAssets(List<String> assetIds, {bool allowNetworkAccess = false});
|
||||||
|
|
||||||
|
@async
|
||||||
|
bool uploadAsset();
|
||||||
|
|
||||||
void cancelHashing();
|
void cancelHashing();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue