mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
fix: use setRequireOriginal on SDK 29 and above (#29072)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
7121e0a75c
commit
29512081db
2 changed files with 54 additions and 17 deletions
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import androidx.exifinterface.media.ExifInterface
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ext.SdkExtensions
|
import android.os.ext.SdkExtensions
|
||||||
|
|
@ -30,6 +29,8 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
|
|
@ -377,7 +378,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
)?.use { cursor -> cursor.count.toLong() } ?: 0L
|
)?.use { cursor -> cursor.count.toLong() } ?: 0L
|
||||||
|
|
||||||
|
|
||||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit) {
|
fun getAssetsForAlbum(
|
||||||
|
albumId: String,
|
||||||
|
updatedTimeCond: Long?,
|
||||||
|
callback: (Result<List<PlatformAsset>>) -> Unit
|
||||||
|
) {
|
||||||
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
|
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,7 +424,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
|
|
||||||
completeWhenActive(callback, Result.success(results))
|
completeWhenActive(callback, Result.success(results))
|
||||||
} catch (e: CancellationException) {
|
} catch (_: CancellationException) {
|
||||||
completeWhenActive(
|
completeWhenActive(
|
||||||
callback, Result.failure(
|
callback, Result.failure(
|
||||||
FlutterError(
|
FlutterError(
|
||||||
|
|
@ -437,23 +442,20 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
|
|
||||||
private suspend fun hashAsset(assetId: String): HashResult {
|
private suspend fun hashAsset(assetId: String): HashResult {
|
||||||
return try {
|
return try {
|
||||||
val assetUri = ContentUris.withAppendedId(
|
|
||||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
|
||||||
assetId.toLong()
|
|
||||||
)
|
|
||||||
|
|
||||||
val digest = MessageDigest.getInstance("SHA-1")
|
val digest = MessageDigest.getInstance("SHA-1")
|
||||||
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
openOriginalStream(assetId).use { inputStream ->
|
||||||
var bytesRead: Int
|
|
||||||
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
||||||
while (inputStream.read(buffer).also { bytesRead = it } > 0) {
|
while (true) {
|
||||||
|
val bytesRead = inputStream.read(buffer)
|
||||||
|
if (bytesRead == -1) break
|
||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
digest.update(buffer, 0, bytesRead)
|
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, Base64.encodeToString(digest.digest(), Base64.NO_WRAP))
|
||||||
HashResult(assetId, null, hashString)
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
|
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -461,6 +463,37 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openOriginalStream(assetId: String): InputStream {
|
||||||
|
val id = assetId.toLong()
|
||||||
|
val collection = when (getMediaType(id)) {
|
||||||
|
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
|
else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
val uri = ContentUris.withAppendedId(collection, id)
|
||||||
|
val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
MediaStore.setRequireOriginal(uri)
|
||||||
|
} else {
|
||||||
|
uri
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.contentResolver.openInputStream(original)
|
||||||
|
?: throw IOException("Cannot open original stream for asset $assetId")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMediaType(id: Long): Int =
|
||||||
|
getCursor(
|
||||||
|
MediaStore.VOLUME_EXTERNAL,
|
||||||
|
"${MediaStore.MediaColumns._ID} = ?",
|
||||||
|
arrayOf(id.toString()),
|
||||||
|
arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
||||||
|
)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))
|
||||||
|
} else {
|
||||||
|
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
||||||
|
}
|
||||||
|
} ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
||||||
|
|
||||||
fun cancelHashing() {
|
fun cancelHashing() {
|
||||||
hashTask?.cancel()
|
hashTask?.cancel()
|
||||||
hashTask = null
|
hashTask = null
|
||||||
|
|
@ -476,8 +509,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
|
||||||
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
syncJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
completeWhenActive(callback, Result.success(work()))
|
completeWhenActive(callback, Result.success(work()))
|
||||||
} catch (e: CancellationException) {
|
} catch (_: CancellationException) {
|
||||||
completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)))
|
completeWhenActive(
|
||||||
|
callback,
|
||||||
|
Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
completeWhenActive(callback, Result.failure(e))
|
completeWhenActive(callback, Result.failure(e))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,12 @@ class HashService {
|
||||||
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
|
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e, s) {
|
||||||
if (e.code == _kHashCancelledCode) {
|
if (e.code == _kHashCancelledCode) {
|
||||||
_log.warning("Hashing cancelled by platform");
|
_log.warning("Hashing cancelled by platform");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_log.severe("Native hashing failed: ${e.code}", e, s);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.severe("Error during hashing", e, s);
|
_log.severe("Error during hashing", e, s);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue