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 getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||
fun uploadAsset(callback: (Result<Boolean>) -> Unit)
|
||||
fun cancelHashing()
|
||||
|
||||
companion object {
|
||||
|
|
@ -467,6 +468,24 @@ interface NativeSyncApi {
|
|||
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 {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ protocol NativeSyncApi {
|
|||
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||
func uploadAsset(completion: @escaping (Result<Bool, Error>) -> Void)
|
||||
func cancelHashing() throws
|
||||
}
|
||||
|
||||
|
|
@ -519,6 +520,21 @@ class NativeSyncApiSetup {
|
|||
} else {
|
||||
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)
|
||||
if let api = api {
|
||||
cancelHashingChannel.setMessageHandler { _, reply in
|
||||
|
|
|
|||
|
|
@ -363,4 +363,38 @@ class NativeSyncApiImpl: NativeSyncApi {
|
|||
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/backup/backup_album.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/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
|
@ -141,6 +142,84 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||
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) {
|
||||
BackupError.none => const SizedBox.shrink(),
|
||||
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 {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$pigeonVar_messageChannelSuffix';
|
||||
|
|
|
|||
|
|
@ -106,5 +106,8 @@ abstract class NativeSyncApi {
|
|||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<HashResult> hashAssets(List<String> assetIds, {bool allowNetworkAccess = false});
|
||||
|
||||
@async
|
||||
bool uploadAsset();
|
||||
|
||||
void cancelHashing();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue