1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

(mobile): ios - calculate hash using CryptoKit (#5976)

* ios: calculate hash using CryptoKit

* chore: remove unused crypto dep

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2024-01-05 16:49:43 +00:00 committed by GitHub
parent ac0cb4a96e
commit d3af2b1f69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 34 deletions

View file

@ -8,6 +8,7 @@
import Flutter
import BackgroundTasks
import path_provider_foundation
import CryptoKit
class BackgroundServicePlugin: NSObject, FlutterPlugin {
@ -102,12 +103,62 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
case "backgroundAppRefreshEnabled":
handleBackgroundRefreshStatus(call: call, result: result)
break
case "digestFiles":
handleDigestFiles(call: call, result: result)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
// Calculates the SHA-1 hash of each file from the list of paths provided
func handleDigestFiles(call: FlutterMethodCall, result: @escaping FlutterResult) {
let bufsize = 2 * 1024 * 1024
// Private error to throw if file cannot be read
enum DigestError: String, LocalizedError {
case NoFileHandle = "Cannot Open File Handle"
public var errorDescription: String? { self.rawValue }
}
// Parse the arguments or else fail
guard let args = call.arguments as? Array<String> else {
print("Cannot parse args as array: \(String(describing: call.arguments))")
result(FlutterError(code: "Malformed",
message: "Received args is not an Array<String>",
details: nil))
return
}
// Compute hash in background thread
DispatchQueue.global(qos: .background).async {
var hashes: [FlutterStandardTypedData?] = Array(repeating: nil, count: args.count)
for i in (0 ..< args.count) {
do {
guard let file = FileHandle(forReadingAtPath: args[i]) else { throw DigestError.NoFileHandle }
var hasher = Insecure.SHA1.init();
while autoreleasepool(invoking: {
let chunk = file.readData(ofLength: bufsize)
guard !chunk.isEmpty else { return false } // EOF
hasher.update(data: chunk)
return true // continue
}) { }
let digest = hasher.finalize()
hashes[i] = FlutterStandardTypedData(bytes: Data(Array(digest.makeIterator())))
} catch {
print("Cannot calculate the digest of the file \(args[i]) due to \(error.localizedDescription)")
}
}
// Return result in main thread
DispatchQueue.main.async {
result(Array(hashes))
}
}
}
// Called by the flutter code when enabled so that we can turn on the backround services
// and save the callback information to communicate on this method channel
public func handleBackgroundEnable(call: FlutterMethodCall, result: FlutterResult) {

View file

@ -132,6 +132,7 @@ class BackgroundService {
}
}
// Yet to be implemented
Future<Uint8List?> digestFile(String path) {
return _foregroundChannel.invokeMethod<Uint8List>("digestFile", [path]);
}

View file

@ -1,7 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
@ -119,37 +117,12 @@ class HashService {
/// Hashes the given files and returns a list of the same length
/// files that could not be hashed have a `null` value
Future<List<Uint8List?>> _hashFiles(List<String> paths) async {
if (Platform.isAndroid) {
final List<Uint8List?>? hashes =
await _backgroundService.digestFiles(paths);
if (hashes == null) {
throw Exception("Hashing ${paths.length} files failed");
}
return hashes;
} else if (Platform.isIOS) {
final List<Uint8List?> result = List.filled(paths.length, null);
for (int i = 0; i < paths.length; i++) {
result[i] = await _hashAssetDart(File(paths[i]));
}
return result;
} else {
throw Exception("_hashFiles implementation missing");
final List<Uint8List?>? hashes =
await _backgroundService.digestFiles(paths);
if (hashes == null) {
throw Exception("Hashing ${paths.length} files failed");
}
}
/// Hashes a single file using Dart's crypto package
Future<Uint8List?> _hashAssetDart(File f) async {
late Digest output;
final sink = sha1.startChunkedConversion(
ChunkedConversionSink<Digest>.withCallback((accumulated) {
output = accumulated.first;
}),
);
await for (final chunk in f.openRead()) {
sink.add(chunk);
}
sink.close();
return Uint8List.fromList(output.bytes);
return hashes;
}
/// Converts [AssetEntity]s that were successfully hashed to [Asset]s

View file

@ -282,7 +282,7 @@ packages:
source: hosted
version: "0.3.3+4"
crypto:
dependency: "direct main"
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab

View file

@ -54,7 +54,6 @@ dependencies:
permission_handler: ^10.2.0
device_info_plus: ^8.1.0
connectivity_plus: ^4.0.1
crypto: ^3.0.3 # TODO remove once native crypto is used on iOS
wakelock_plus: ^1.1.1
flutter_local_notifications: ^15.1.0+1
timezone: ^0.9.2