From 0281de7ff663d630cecc2706cf0c8e03cc2a88b4 Mon Sep 17 00:00:00 2001
From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Date: Sat, 23 May 2026 05:41:14 +0530
Subject: [PATCH] cleanup
---
.../android/app/src/main/AndroidManifest.xml | 2 -
.../alextran/immich/media/MediaStoreUtils.kt | 148 --------
.../app/alextran/immich/sync/Messages.g.kt | 21 --
.../alextran/immich/sync/MessagesImplBase.kt | 80 +----
.../immich/viewintent/ViewIntentPlugin.kt | 129 ++++---
mobile/ios/Runner/Sync/Messages.g.swift | 20 --
mobile/ios/Runner/Sync/MessagesImpl.swift | 7 -
.../entities/merged_asset.drift | 147 --------
.../entities/merged_asset.drift.dart | 69 ----
.../repositories/timeline.repository.dart | 29 --
mobile/lib/platform/native_sync_api.g.dart | 315 ++++++++++--------
.../upload_action_button.widget.dart | 12 -
.../asset_viewer/asset_viewer.page.dart | 6 -
.../main_timeline_handoff.provider.dart | 270 ---------------
.../view_intent_handler_android.dart | 50 +--
.../view_intent_asset_resolver.service.dart | 239 ++-----------
mobile/pigeon/native_sync_api.dart | 4 -
.../main_timeline_handoff_provider_test.dart | 221 ------------
.../view_intent_handler_android_test.dart | 91 ++---
.../view_intent_asset_resolver_test.dart | 161 ++-------
20 files changed, 323 insertions(+), 1698 deletions(-)
delete mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
delete mode 100644 mobile/lib/providers/asset_viewer/main_timeline_handoff.provider.dart
delete mode 100644 mobile/test/providers/asset_viewer/main_timeline_handoff_provider_test.dart
diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index c44213a585..1b8d2a97fb 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -94,7 +94,6 @@
-
@@ -102,7 +101,6 @@
-
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
deleted file mode 100644
index 36f207bd44..0000000000
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-package app.alextran.immich.media
-
-import android.content.Context
-import android.net.Uri
-import android.os.Build
-import android.provider.MediaStore
-import android.provider.OpenableColumns
-import android.util.Log
-import android.webkit.MimeTypeMap
-
-private const val TAG = "MediaStoreUtils"
-
-object MediaStoreUtils {
- private fun externalFilesUri(): Uri =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
- } else {
- MediaStore.Files.getContentUri("external")
- }
-
- fun contentUriForMimeType(mimeType: String): Uri =
- when {
- mimeType.startsWith("image/") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- mimeType.startsWith("video/") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
- mimeType.startsWith("audio/") -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
- else -> externalFilesUri()
- }
-
- fun contentUriForAssetType(type: Int): Uri =
- when (type) {
- // same order as AssetType from dart
- 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
- 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
- else -> externalFilesUri()
- }
-
- fun resolveMimeType(context: Context, uri: Uri, fallbackMimeType: String? = null): String? {
- return context.contentResolver.getType(uri)
- ?: fallbackMimeType
- ?: resolveMimeTypeFromDisplayName(context, uri)
- ?: resolveMimeTypeFromPath(uri.path)
- ?: resolveMimeTypeFromPath(uri.toString())
- }
-
- fun resolveLocalIdByRelativePath(context: Context, path: String, mimeType: String): String? {
- val fileName = path.substringAfterLast('/', missingDelimiterValue = path)
- val parent = path.substringBeforeLast('/', "").let { if (it.isEmpty()) "" else "$it/" }
- if (fileName.isBlank()) return null
-
- val (selection, args) =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.RELATIVE_PATH}=?" to arrayOf(fileName, parent)
- } else {
- "${MediaStore.MediaColumns.DISPLAY_NAME}=?" to arrayOf(fileName)
- }
-
- return queryLatestId(
- context = context,
- tableUri = contentUriForMimeType(mimeType),
- selection = selection,
- selectionArgs = args,
- )
- }
-
- fun resolveLocalIdByNameAndSize(context: Context, uri: Uri, mimeType: String): String? {
- val metaProjection = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
- val (displayName, size) =
- try {
- context.contentResolver.query(uri, metaProjection, null, null, null)?.use { cursor ->
- if (!cursor.moveToFirst()) return null
- val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
- val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE)
- val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null
- val bytes = if (sizeIdx >= 0) cursor.getLong(sizeIdx) else -1L
- if (name.isNullOrBlank() || bytes < 0) return null
- name to bytes
- } ?: return null
- } catch (_: Exception) {
- return null
- }
-
- return queryLatestId(
- context = context,
- tableUri = contentUriForMimeType(mimeType),
- selection = "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.SIZE}=?",
- selectionArgs = arrayOf(displayName, size.toString()),
- )
- }
-
- private fun resolveMimeTypeFromDisplayName(context: Context, uri: Uri): String? {
- return try {
- context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
- if (!cursor.moveToFirst()) {
- return null
- }
-
- val displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
- if (displayNameIndex < 0) {
- return null
- }
-
- resolveMimeTypeFromPath(cursor.getString(displayNameIndex))
- }
- } catch (e: Exception) {
- Log.w(TAG, "Failed to resolve MIME type from display name: $uri", e)
- null
- }
- }
-
- private fun resolveMimeTypeFromPath(path: String?): String? {
- if (path.isNullOrBlank()) {
- return null
- }
-
- val extension = path.substringAfterLast('.', missingDelimiterValue = "").substringBefore('?').substringBefore('#')
- if (extension.isBlank()) {
- return null
- }
-
- return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.lowercase())
- }
-
- private fun queryLatestId(
- context: Context,
- tableUri: Uri,
- selection: String,
- selectionArgs: Array,
- ): String? {
- return try {
- context.contentResolver
- .query(
- tableUri,
- arrayOf(MediaStore.MediaColumns._ID),
- selection,
- selectionArgs,
- "${MediaStore.MediaColumns.DATE_MODIFIED} DESC",
- )?.use { cursor ->
- if (!cursor.moveToFirst()) return null
- val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
- if (idIndex < 0) return null
- cursor.getLong(idIndex).toString()
- }
- } catch (_: Exception) {
- null
- }
- }
-}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
index 1daa1fbaa7..345302026d 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt
@@ -551,7 +551,6 @@ interface NativeSyncApi {
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List
fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit)
- fun hashFiles(paths: List, callback: (Result>) -> Unit)
fun cancelHashing()
fun getTrashedAssets(): Map>
fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit)
@@ -718,26 +717,6 @@ interface NativeSyncApi {
channel.setMessageHandler(null)
}
}
- run {
- val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashFiles$separatedMessageChannelSuffix", codec, taskQueue)
- if (api != null) {
- channel.setMessageHandler { message, reply ->
- val args = message as List
- val pathsArg = args[0] as List
- api.hashFiles(pathsArg) { result: Result> ->
- 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(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec)
if (api != null) {
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
index bc2138546e..1f5ff2529e 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt
@@ -30,8 +30,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.io.File
-import java.io.FileInputStream
-import java.io.InputStream
import java.security.MessageDigest
import kotlin.coroutines.cancellation.CancellationException
@@ -422,44 +420,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
}
}
- fun hashFiles(
- paths: List,
- callback: (Result>) -> Unit
- ) {
- if (paths.isEmpty()) {
- completeWhenActive(callback, Result.success(emptyList()))
- return
- }
-
- hashTask?.cancel()
- hashTask = CoroutineScope(Dispatchers.IO).launch {
- try {
- val results = paths.map { path ->
- async {
- hashSemaphore.withPermit {
- ensureActive()
- hashFile(path)
- }
- }
- }.awaitAll()
-
- completeWhenActive(callback, Result.success(results))
- } catch (e: CancellationException) {
- completeWhenActive(
- callback, Result.failure(
- FlutterError(
- HASHING_CANCELLED_CODE,
- "Hashing operation was cancelled",
- null
- )
- )
- )
- } catch (e: Exception) {
- completeWhenActive(callback, Result.failure(e))
- }
- }
- }
-
private suspend fun hashAsset(assetId: String): HashResult {
return try {
val assetUri = ContentUris.withAppendedId(
@@ -467,10 +427,17 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
assetId.toLong()
)
- val hashString = ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
- hashInputStream(inputStream)
+ val digest = MessageDigest.getInstance("SHA-1")
+ ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
+ var bytesRead: Int
+ val buffer = ByteArray(HASH_BUFFER_SIZE)
+ while (inputStream.read(buffer).also { bytesRead = it } > 0) {
+ currentCoroutineContext().ensureActive()
+ digest.update(buffer, 0, bytesRead)
+ }
} ?: return HashResult(assetId, "Cannot open input stream for asset", null)
+ val hashString = Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
HashResult(assetId, null, hashString)
} catch (e: SecurityException) {
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
@@ -479,35 +446,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
}
}
- private suspend fun hashFile(path: String): HashResult {
- return try {
- val file = File(path)
- if (!file.exists()) {
- return HashResult(path, "File does not exist", null)
- }
-
- val hashString = FileInputStream(file).use { inputStream ->
- hashInputStream(inputStream)
- }
- HashResult(path, null, hashString)
- } catch (e: SecurityException) {
- HashResult(path, "Permission denied accessing file: ${e.message}", null)
- } catch (e: Exception) {
- HashResult(path, "Failed to hash file: ${e.message}", null)
- }
- }
-
- private suspend fun hashInputStream(inputStream: InputStream): String {
- val digest = MessageDigest.getInstance("SHA-1")
- var bytesRead: Int
- val buffer = ByteArray(HASH_BUFFER_SIZE)
- while (inputStream.read(buffer).also { bytesRead = it } > 0) {
- currentCoroutineContext().ensureActive()
- digest.update(buffer, 0, bytesRead)
- }
- return Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
- }
-
fun cancelHashing() {
hashTask?.cancel()
hashTask = null
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
index 9773906682..58117ed37e 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
@@ -5,10 +5,12 @@ import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.Build
import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.provider.OpenableColumns
import android.util.Log
import android.webkit.MimeTypeMap
-import app.alextran.immich.media.MediaStoreUtils
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -83,7 +85,7 @@ class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentL
ioScope.launch {
try {
- val mimeType = MediaStoreUtils.resolveMimeType(context, uri, intent.type)
+ val mimeType = context.contentResolver.getType(uri) ?: intent.type
if (mimeType == null || (!mimeType.startsWith("image/") && !mimeType.startsWith("video/"))) {
callback(Result.success(null))
return@launch
@@ -121,52 +123,22 @@ class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentL
}
private fun extractLocalAssetId(context: Context, uri: Uri, mimeType: String): String? {
- if (uri.scheme != "content") {
- return null
- }
-
- val fromDocumentUri = tryExtractDocumentLocalAssetId(context, uri, mimeType)
- if (fromDocumentUri != null) {
- return fromDocumentUri
- }
-
- val fromContentUri = tryParseContentUriId(uri)
- if (fromContentUri != null) {
- return fromContentUri
- }
-
- val fromPathSegment = tryParseLastPathSegmentId(uri)
- if (fromPathSegment != null) {
- return fromPathSegment
- }
-
- return MediaStoreUtils.resolveLocalIdByNameAndSize(context, uri, mimeType)
+ return tryExtractDocumentLocalAssetId(context, uri)
+ ?: tryParseContentUriId(uri)
+ ?: tryParseLastPathSegmentId(uri)
+ ?: resolveLocalIdByNameAndSize(context, uri, mimeType)
}
- private fun tryExtractDocumentLocalAssetId(context: Context, uri: Uri, mimeType: String): String? {
- try {
- if (!DocumentsContract.isDocumentUri(context, uri)) {
- return null
- }
-
+ private fun tryExtractDocumentLocalAssetId(context: Context, uri: Uri): String? {
+ return try {
+ if (!DocumentsContract.isDocumentUri(context, uri)) return null
val docId = DocumentsContract.getDocumentId(uri)
- if (docId.startsWith("raw:")) {
- return null
- }
-
- if (docId.isBlank()) {
- return null
- }
-
+ if (docId.isBlank() || docId.startsWith("raw:")) return null
val parsed = docId.substringAfter(':', docId)
- if (parsed.all(Char::isDigit)) {
- return parsed
- }
-
- return MediaStoreUtils.resolveLocalIdByRelativePath(context, parsed, mimeType)
+ if (parsed.isNotEmpty() && parsed.all(Char::isDigit)) parsed else null
} catch (e: Exception) {
Log.w(TAG, "Failed to resolve local asset id from document URI: $uri", e)
- return null
+ null
}
}
@@ -187,33 +159,7 @@ class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentL
private fun copyUriToTempFile(context: Context, uri: Uri, mimeType: String): File? {
return try {
- val normalizedMimeType = mimeType.substringBefore(';').lowercase()
- val mimeTypeExtension = MimeTypeMap
- .getSingleton()
- .getExtensionFromMimeType(normalizedMimeType)
- ?.let { ".$it" }
-
- val extension = when {
- normalizedMimeType.startsWith("image/") -> {
- when {
- normalizedMimeType.contains("jpeg") || normalizedMimeType.contains("jpg") -> ".jpg"
- normalizedMimeType.contains("png") -> ".png"
- normalizedMimeType.contains("gif") -> ".gif"
- normalizedMimeType.contains("webp") -> ".webp"
- else -> mimeTypeExtension ?: ".jpg"
- }
- }
- normalizedMimeType.startsWith("video/") -> {
- when {
- normalizedMimeType.contains("mp4") -> ".mp4"
- normalizedMimeType.contains("webm") -> ".webm"
- normalizedMimeType.contains("3gp") -> ".3gp"
- else -> mimeTypeExtension ?: ".mp4"
- }
- }
- else -> mimeTypeExtension ?: ".tmp"
- }
-
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" }
val tempFile = File.createTempFile("view_intent_", extension, context.cacheDir)
context.contentResolver.openInputStream(uri)?.use { inputStream ->
FileOutputStream(tempFile).use { outputStream ->
@@ -225,4 +171,49 @@ class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentL
null
}
}
+
+ private fun resolveLocalIdByNameAndSize(context: Context, uri: Uri, mimeType: String): String? {
+ val metaProjection = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
+ val (displayName, size) =
+ try {
+ context.contentResolver.query(uri, metaProjection, null, null, null)?.use { cursor ->
+ if (!cursor.moveToFirst()) return null
+ val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE)
+ val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null
+ val bytes = if (sizeIdx >= 0) cursor.getLong(sizeIdx) else -1L
+ if (name.isNullOrBlank() || bytes < 0) return null
+ name to bytes
+ } ?: return null
+ } catch (_: Exception) {
+ return null
+ }
+
+ val tableUri = when {
+ mimeType.startsWith("image/") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ mimeType.startsWith("video/") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ } else {
+ MediaStore.Files.getContentUri("external")
+ }
+ }
+ return try {
+ context.contentResolver
+ .query(
+ tableUri,
+ arrayOf(MediaStore.MediaColumns._ID),
+ "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.SIZE}=?",
+ arrayOf(displayName, size.toString()),
+ "${MediaStore.MediaColumns.DATE_MODIFIED} DESC",
+ )?.use { cursor ->
+ if (!cursor.moveToFirst()) return null
+ val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
+ if (idIndex < 0) return null
+ cursor.getLong(idIndex).toString()
+ }
+ } catch (_: Exception) {
+ null
+ }
+ }
}
diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift
index 0573936625..d18a153bb7 100644
--- a/mobile/ios/Runner/Sync/Messages.g.swift
+++ b/mobile/ios/Runner/Sync/Messages.g.swift
@@ -535,7 +535,6 @@ 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 hashFiles(paths: [String], completion: @escaping (Result<[HashResult], Error>) -> Void)
func cancelHashing() throws
func getTrashedAssets() throws -> [String: [PlatformAsset]]
func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void)
@@ -695,25 +694,6 @@ class NativeSyncApiSetup {
} else {
hashAssetsChannel.setMessageHandler(nil)
}
- let hashFilesChannel = taskQueue == nil
- ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashFiles\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
- : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashFiles\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
- if let api = api {
- hashFilesChannel.setMessageHandler { message, reply in
- let args = message as! [Any?]
- let pathsArg = args[0] as! [String]
- api.hashFiles(paths: pathsArg) { result in
- switch result {
- case .success(let res):
- reply(wrapResult(res))
- case .failure(let error):
- reply(wrapError(error))
- }
- }
- }
- } else {
- hashFilesChannel.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
diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift
index 249abce6c9..377e8197a1 100644
--- a/mobile/ios/Runner/Sync/MessagesImpl.swift
+++ b/mobile/ios/Runner/Sync/MessagesImpl.swift
@@ -319,13 +319,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
}
- func hashFiles(paths: [String], completion: @escaping (Result<[HashResult], Error>) -> Void) {
- let results = paths.map { path in
- HashResult(assetId: path, error: "Not implemented on iOS", hash: nil)
- }
- completeWhenActive(for: completion, with: .success(results))
- }
-
func cancelHashing() {
hashTask?.cancel()
hashTask = nil
diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift b/mobile/lib/infrastructure/entities/merged_asset.drift
index ff17c5c9ea..d0321ab1ef 100644
--- a/mobile/lib/infrastructure/entities/merged_asset.drift
+++ b/mobile/lib/infrastructure/entities/merged_asset.drift
@@ -139,150 +139,3 @@ FROM
)
GROUP BY bucket_date
ORDER BY bucket_date DESC;
-
-mergedAssetIndexByLocalId:
-SELECT
- idx
-FROM (
- SELECT
- local_id,
- ROW_NUMBER() OVER (ORDER BY created_at DESC) - 1 as idx
- FROM (
- SELECT
- (SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id,
- rae.created_at as created_at
- FROM
- remote_asset_entity rae
- LEFT JOIN
- stack_entity se ON rae.stack_id = se.id
- WHERE
- rae.deleted_at IS NULL
- AND rae.visibility = 0 -- timeline visibility
- AND rae.owner_id IN :user_ids
- AND (
- rae.stack_id IS NULL
- OR rae.id = se.primary_asset_id
- )
-
- UNION ALL
-
- SELECT
- lae.id as local_id,
- lae.created_at as created_at
- FROM
- local_asset_entity lae
- WHERE NOT EXISTS (
- SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN :user_ids
- )
- AND EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
- )
- AND NOT EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
- )
- )
-)
-WHERE local_id = :local_asset_id
-LIMIT 1;
-
-mergedAssetIndexByChecksum:
-SELECT
- idx
-FROM (
- SELECT
- checksum,
- ROW_NUMBER() OVER (ORDER BY created_at DESC) - 1 as idx
- FROM (
- SELECT
- rae.checksum as checksum,
- rae.created_at as created_at
- FROM
- remote_asset_entity rae
- LEFT JOIN
- stack_entity se ON rae.stack_id = se.id
- WHERE
- rae.deleted_at IS NULL
- AND rae.visibility = 0 -- timeline visibility
- AND rae.owner_id IN :user_ids
- AND (
- rae.stack_id IS NULL
- OR rae.id = se.primary_asset_id
- )
-
- UNION ALL
-
- SELECT
- lae.checksum as checksum,
- lae.created_at as created_at
- FROM
- local_asset_entity lae
- WHERE NOT EXISTS (
- SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN :user_ids
- )
- AND EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
- )
- AND NOT EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
- )
- )
-)
-WHERE checksum = :checksum
-LIMIT 1;
-
-mergedAssetIndexByRemoteId:
-SELECT
- idx
-FROM (
- SELECT
- remote_id,
- ROW_NUMBER() OVER (ORDER BY created_at DESC) - 1 as idx
- FROM (
- SELECT
- rae.id as remote_id,
- rae.created_at as created_at
- FROM
- remote_asset_entity rae
- LEFT JOIN
- stack_entity se ON rae.stack_id = se.id
- WHERE
- rae.deleted_at IS NULL
- AND rae.visibility = 0 -- timeline visibility
- AND rae.owner_id IN :user_ids
- AND (
- rae.stack_id IS NULL
- OR rae.id = se.primary_asset_id
- )
-
- UNION ALL
-
- SELECT
- NULL as remote_id,
- lae.created_at as created_at
- FROM
- local_asset_entity lae
- WHERE NOT EXISTS (
- SELECT 1 FROM remote_asset_entity rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN :user_ids
- )
- AND EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 0 -- selected
- )
- AND NOT EXISTS (
- SELECT 1 FROM local_album_asset_entity laa
- INNER JOIN local_album_entity la on laa.album_id = la.id
- WHERE laa.asset_id = lae.id AND la.backup_selection = 2 -- excluded
- )
- )
-)
-WHERE remote_id = :remote_id
-LIMIT 1;
diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift.dart b/mobile/lib/infrastructure/entities/merged_asset.drift.dart
index 29e0ec0298..2d05ef6ceb 100644
--- a/mobile/lib/infrastructure/entities/merged_asset.drift.dart
+++ b/mobile/lib/infrastructure/entities/merged_asset.drift.dart
@@ -101,75 +101,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
);
}
- i0.Selectable mergedAssetIndexByLocalId({
- required List userIds,
- String? localAssetId,
- }) {
- var $arrayStartIndex = 2;
- final expandeduserIds = $expandVar($arrayStartIndex, userIds.length);
- $arrayStartIndex += userIds.length;
- return customSelect(
- 'SELECT idx FROM (SELECT local_id, ROW_NUMBER()OVER (ORDER BY created_at DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) - 1 AS idx FROM (SELECT (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.created_at AS created_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT lae.id AS local_id, lae.created_at AS created_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2))) WHERE local_id = ?1 LIMIT 1',
- variables: [
- i0.Variable(localAssetId),
- for (var $ in userIds) i0.Variable($),
- ],
- readsFrom: {
- localAssetEntity,
- remoteAssetEntity,
- stackEntity,
- localAlbumAssetEntity,
- localAlbumEntity,
- },
- ).map((i0.QueryRow row) => row.read('idx'));
- }
-
- i0.Selectable mergedAssetIndexByChecksum({
- required List userIds,
- String? checksum,
- }) {
- var $arrayStartIndex = 2;
- final expandeduserIds = $expandVar($arrayStartIndex, userIds.length);
- $arrayStartIndex += userIds.length;
- return customSelect(
- 'SELECT idx FROM (SELECT checksum, ROW_NUMBER()OVER (ORDER BY created_at DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) - 1 AS idx FROM (SELECT rae.checksum AS checksum, rae.created_at AS created_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT lae.checksum AS checksum, lae.created_at AS created_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2))) WHERE checksum = ?1 LIMIT 1',
- variables: [
- i0.Variable(checksum),
- for (var $ in userIds) i0.Variable($),
- ],
- readsFrom: {
- remoteAssetEntity,
- stackEntity,
- localAssetEntity,
- localAlbumAssetEntity,
- localAlbumEntity,
- },
- ).map((i0.QueryRow row) => row.read('idx'));
- }
-
- i0.Selectable mergedAssetIndexByRemoteId({
- required List userIds,
- String? remoteId,
- }) {
- var $arrayStartIndex = 2;
- final expandeduserIds = $expandVar($arrayStartIndex, userIds.length);
- $arrayStartIndex += userIds.length;
- return customSelect(
- 'SELECT idx FROM (SELECT remote_id, ROW_NUMBER()OVER (ORDER BY created_at DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) - 1 AS idx FROM (SELECT rae.id AS remote_id, rae.created_at AS created_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.created_at AS created_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2))) WHERE remote_id = ?1 LIMIT 1',
- variables: [
- i0.Variable(remoteId),
- for (var $ in userIds) i0.Variable($),
- ],
- readsFrom: {
- remoteAssetEntity,
- stackEntity,
- localAssetEntity,
- localAlbumAssetEntity,
- localAlbumEntity,
- },
- ).map((i0.QueryRow row) => row.read('idx'));
- }
-
i4.$RemoteAssetEntityTable get remoteAssetEntity => i1.ReadDatabaseContainer(
attachedDatabase,
).resultSet('remote_asset_entity');
diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart
index c9549f4105..bf707ad0bd 100644
--- a/mobile/lib/infrastructure/repositories/timeline.repository.dart
+++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart
@@ -679,35 +679,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}
}
- Future getMainTimelineIndexByChecksum(List userIds, String checksum) async {
- if (userIds.isEmpty) {
- return null;
- }
- final result = await _db.mergedAssetDrift
- .mergedAssetIndexByChecksum(userIds: userIds, checksum: checksum)
- .getSingleOrNull();
- return result;
- }
-
- Future getMainTimelineIndexByLocalId(List userIds, String localAssetId) async {
- if (userIds.isEmpty) {
- return null;
- }
- final result = await _db.mergedAssetDrift
- .mergedAssetIndexByLocalId(userIds: userIds, localAssetId: localAssetId)
- .getSingleOrNull();
- return result;
- }
-
- Future getMainTimelineIndexByRemoteId(List userIds, String remoteAssetId) async {
- if (userIds.isEmpty) {
- return null;
- }
- final result = await _db.mergedAssetDrift
- .mergedAssetIndexByRemoteId(userIds: userIds, remoteId: remoteAssetId)
- .getSingleOrNull();
- return result;
- }
}
List _generateBuckets(int count) {
diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart
index ffd7dc0694..18e196ae8b 100644
--- a/mobile/lib/platform/native_sync_api.g.dart
+++ b/mobile/lib/platform/native_sync_api.g.dart
@@ -9,14 +9,22 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List;
import 'package:flutter/services.dart';
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
-Object? _extractReplyValueOrThrow(List