feat(mobile): hash assets in isolate (#18924)

This commit is contained in:
shenlong
2025-06-06 11:23:05 +05:30
committed by GitHub
parent b46e066cc2
commit ce6631f7e0
34 changed files with 1254 additions and 428 deletions

View File

@@ -247,6 +247,7 @@ interface NativeSyncApi {
fun getAlbums(): List<PlatformAlbum>
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
fun hashPaths(paths: List<String>): List<ByteArray?>
companion object {
/** The codec used by NativeSyncApi. */
@@ -388,6 +389,23 @@ interface NativeSyncApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashPaths$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pathsArg = args[0] as List<String>
val wrapped: List<Any?> = try {
listOf(api.hashPaths(pathsArg))
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View File

@@ -4,7 +4,10 @@ import android.annotation.SuppressLint
import android.content.Context
import android.database.Cursor
import android.provider.MediaStore
import android.util.Log
import java.io.File
import java.io.FileInputStream
import java.security.MessageDigest
sealed class AssetResult {
data class ValidAsset(val asset: PlatformAsset, val albumId: String) : AssetResult()
@@ -16,6 +19,8 @@ open class NativeSyncApiImplBase(context: Context) {
private val ctx: Context = context.applicationContext
companion object {
private const val TAG = "NativeSyncApiImplBase"
const val MEDIA_SELECTION =
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
val MEDIA_SELECTION_ARGS = arrayOf(
@@ -34,6 +39,8 @@ open class NativeSyncApiImplBase(context: Context) {
MediaStore.MediaColumns.BUCKET_ID,
MediaStore.MediaColumns.DURATION
)
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
}
protected fun getCursor(
@@ -174,4 +181,24 @@ open class NativeSyncApiImplBase(context: Context) {
.mapNotNull { result -> (result as? AssetResult.ValidAsset)?.asset }
.toList()
}
fun hashPaths(paths: List<String>): List<ByteArray?> {
val buffer = ByteArray(HASH_BUFFER_SIZE)
val digest = MessageDigest.getInstance("SHA-1")
return paths.map { path ->
try {
FileInputStream(path).use { file ->
var bytesRead: Int
while (file.read(buffer).also { bytesRead = it } > 0) {
digest.update(buffer, 0, bytesRead)
}
}
digest.digest()
} catch (e: Exception) {
Log.w(TAG, "Failed to hash file $path: $e")
null
}
}
}
}