diff --git a/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/MainActivity.kt b/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/MainActivity.kt index c2e8c3f565..8be7edd0dc 100644 --- a/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/MainActivity.kt +++ b/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/MainActivity.kt @@ -1,5 +1,15 @@ package com.alextran.immich +import ImmichHostService +import com.alextran.immich.platform.ImmichHostServiceImpl import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine -class MainActivity: FlutterActivity() +class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + // Register piegon handler + ImmichHostService.setUp(flutterEngine.dartExecutor.binaryMessenger, ImmichHostServiceImpl()) + } +} diff --git a/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/platform/MessagesImpl.kt b/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/platform/MessagesImpl.kt new file mode 100644 index 0000000000..f001de754a --- /dev/null +++ b/mobile-v2/android/app/src/main/kotlin/com/alextran/immich/platform/MessagesImpl.kt @@ -0,0 +1,47 @@ +package com.alextran.immich.platform + +import ImmichHostService +import android.util.Log +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.io.FileInputStream +import java.security.MessageDigest + +class ImmichHostServiceImpl: ImmichHostService { + + @OptIn(DelicateCoroutinesApi::class) + override fun digestFiles(paths: List, callback: (Result?>) -> Unit) { + GlobalScope.launch(Dispatchers.IO) { + val buf = ByteArray(Companion.BUFFER_SIZE) + val digest: MessageDigest = MessageDigest.getInstance("SHA-1") + val hashes = arrayOfNulls(paths.size) + for (i in paths.indices) { + val path = paths[i] + var len = 0 + try { + val file = FileInputStream(path) + file.use { assetFile -> + while (true) { + len = assetFile.read(buf) + if (len != Companion.BUFFER_SIZE) break + digest.update(buf) + } + } + digest.update(buf, 0, len) + hashes[i] = digest.digest() + } catch (e: Exception) { + // skip this file + Log.w(TAG, "Failed to hash file ${paths[i]}: $e") + } + } + callback(Result.success(hashes.asList())) + } + } + + companion object { + private const val BUFFER_SIZE = 2 * 1024 * 1024; + private const val TAG = "ImmichHostServiceImpl" + } +} diff --git a/mobile-v2/ios/Podfile b/mobile-v2/ios/Podfile index f38ac9619b..007a691968 100644 --- a/mobile-v2/ios/Podfile +++ b/mobile-v2/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/mobile-v2/ios/Podfile.lock b/mobile-v2/ios/Podfile.lock index d2bd383df6..a4bc96193b 100644 --- a/mobile-v2/ios/Podfile.lock +++ b/mobile-v2/ios/Podfile.lock @@ -83,6 +83,6 @@ SPEC CHECKSUMS: sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe -PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d +PODFILE CHECKSUM: 47bc0bc2c8210ec5b4886fa2f4d1046a536e3e17 COCOAPODS: 1.15.2 diff --git a/mobile-v2/ios/Runner.xcodeproj/project.pbxproj b/mobile-v2/ios/Runner.xcodeproj/project.pbxproj index cd1ed4883a..54abe03cd3 100644 --- a/mobile-v2/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile-v2/ios/Runner.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B2FE78A32C9633490065752A /* Messages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FE78A02C9633490065752A /* Messages.g.swift */; }; + B2FE78A42C9633490065752A /* MessagesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FE78A12C9633490065752A /* MessagesImpl.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,6 +62,8 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B0875BFC00218C7E86E76B26 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B2FE78A02C9633490065752A /* Messages.g.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messages.g.swift; sourceTree = ""; }; + B2FE78A12C9633490065752A /* MessagesImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesImpl.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -81,7 +85,6 @@ 5743DC0B43F97DC290E40819 /* Pods-Runner.release.xcconfig */, B0875BFC00218C7E86E76B26 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -136,6 +139,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + B2FE78A22C9633490065752A /* Platform */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -148,6 +152,15 @@ path = Runner; sourceTree = ""; }; + B2FE78A22C9633490065752A /* Platform */ = { + isa = PBXGroup; + children = ( + B2FE78A02C9633490065752A /* Messages.g.swift */, + B2FE78A12C9633490065752A /* MessagesImpl.swift */, + ); + path = Platform; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -197,7 +210,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1540; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -336,8 +349,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B2FE78A32C9633490065752A /* Messages.g.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + B2FE78A42C9633490065752A /* MessagesImpl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -395,6 +410,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -425,12 +441,12 @@ }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = B0875BFC00218C7E86E76B26 /* Pods-Runner.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 7AM53GU2W3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "immich-v2"; @@ -498,6 +514,7 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -520,6 +537,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -577,6 +595,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -609,12 +628,12 @@ }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + baseConfigurationReference = 2DB7C9F14E5C41B0C29A9694 /* Pods-Runner.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 7AM53GU2W3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "immich-v2"; @@ -636,12 +655,12 @@ }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 5743DC0B43F97DC290E40819 /* Pods-Runner.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2F67MQ8R79; + DEVELOPMENT_TEAM = 7AM53GU2W3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "immich-v2"; diff --git a/mobile-v2/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile-v2/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5dfe1..550ef0c1fd 100644 --- a/mobile-v2/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile-v2/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { GeneratedPluginRegistrant.register(with: self) + + // Register piegon handler + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + ImmichHostServiceSetup.setUp(binaryMessenger: controller.binaryMessenger, api: ImmichHostServiceImpl()) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/mobile-v2/ios/Runner/Platform/MessagesImpl.swift b/mobile-v2/ios/Runner/Platform/MessagesImpl.swift new file mode 100644 index 0000000000..6beede055c --- /dev/null +++ b/mobile-v2/ios/Runner/Platform/MessagesImpl.swift @@ -0,0 +1,39 @@ +import CryptoKit + +class ImmichHostServiceImpl: ImmichHostService { + func digestFiles(paths: [String], completion: @escaping (Result<[FlutterStandardTypedData?]?, Error>) -> Void) { + 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 } + } + + // Compute hash in background thread + DispatchQueue.global(qos: .background).async { + var hashes: [FlutterStandardTypedData?] = Array(repeating: nil, count: paths.count) + for i in (0 ..< paths.count) { + do { + guard let file = FileHandle(forReadingAtPath: paths[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 \(paths[i]) due to \(error.localizedDescription)") + } + } + + // Return result in main thread + DispatchQueue.main.async { + completion(.success(Array(hashes))) + } + } + } +} diff --git a/mobile-v2/lib/platform/messages.dart b/mobile-v2/lib/platform/messages.dart new file mode 100644 index 0000000000..111274990c --- /dev/null +++ b/mobile-v2/lib/platform/messages.dart @@ -0,0 +1,17 @@ +// ignore: depend_on_referenced_packages +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/platform/messages.g.dart', + dartOptions: DartOptions(), + kotlinOut: + 'android/app/src/main/kotlin/com/alextran/immich/platform/Messages.g.kt', + kotlinOptions: KotlinOptions(), + swiftOut: 'ios/Runner/Platform/Messages.g.swift', + swiftOptions: SwiftOptions(), +)) +@HostApi() +abstract class ImmichHostService { + @async + List? digestFiles(List paths); +}