mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
resolve merge conflicts
use updated approach for calculating checksums
This commit is contained in:
parent
bd9e4871ec
commit
42f99e8039
8 changed files with 364 additions and 116 deletions
|
|
@ -89,7 +89,8 @@ data class PlatformAsset (
|
||||||
val height: Long? = null,
|
val height: Long? = null,
|
||||||
val durationInSeconds: Long,
|
val durationInSeconds: Long,
|
||||||
val orientation: Long,
|
val orientation: Long,
|
||||||
val isFavorite: Boolean
|
val isFavorite: Boolean,
|
||||||
|
val size: Long? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -104,7 +105,8 @@ data class PlatformAsset (
|
||||||
val durationInSeconds = pigeonVar_list[7] as Long
|
val durationInSeconds = pigeonVar_list[7] as Long
|
||||||
val orientation = pigeonVar_list[8] as Long
|
val orientation = pigeonVar_list[8] as Long
|
||||||
val isFavorite = pigeonVar_list[9] as Boolean
|
val isFavorite = pigeonVar_list[9] as Boolean
|
||||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite)
|
val size = pigeonVar_list[10] as Long?
|
||||||
|
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
|
|
@ -119,6 +121,7 @@ data class PlatformAsset (
|
||||||
durationInSeconds,
|
durationInSeconds,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|
@ -243,6 +246,40 @@ data class HashResult (
|
||||||
|
|
||||||
override fun hashCode(): Int = toList().hashCode()
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class TrashedAssetParams (
|
||||||
|
val id: String,
|
||||||
|
val type: Long,
|
||||||
|
val albumId: String? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): TrashedAssetParams {
|
||||||
|
val id = pigeonVar_list[0] as String
|
||||||
|
val type = pigeonVar_list[1] as Long
|
||||||
|
val albumId = pigeonVar_list[2] as String?
|
||||||
|
return TrashedAssetParams(id, type, albumId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
albumId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is TrashedAssetParams) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return MessagesPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
|
|
@ -266,6 +303,11 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||||
HashResult.fromList(it)
|
HashResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
133.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
TrashedAssetParams.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> super.readValueOfType(type, buffer)
|
else -> super.readValueOfType(type, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -287,6 +329,10 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||||
stream.write(132)
|
stream.write(132)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
|
is TrashedAssetParams -> {
|
||||||
|
stream.write(133)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -305,6 +351,8 @@ interface NativeSyncApi {
|
||||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||||
fun cancelHashing()
|
fun cancelHashing()
|
||||||
|
fun getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||||
|
fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>, callback: (Result<List<HashResult>>) -> Unit)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by NativeSyncApi. */
|
/** The codec used by NativeSyncApi. */
|
||||||
|
|
@ -483,6 +531,44 @@ interface NativeSyncApi {
|
||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val albumIdArg = args[0] as String
|
||||||
|
val updatedTimeCondArg = args[1] as Long?
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.getTrashedAssetsForAlbum(albumIdArg, updatedTimeCondArg))
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val trashedAssetsArg = args[0] as List<TrashedAssetParams>
|
||||||
|
api.hashTrashedAssets(trashedAssetsArg) { result: Result<List<HashResult>> ->
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
|
||||||
throw IllegalStateException("Method not supported on this Android version.")
|
throw IllegalStateException("Method not supported on this Android version.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
|
override fun hashTrashedAssets(
|
||||||
|
trashedAssets: List<TrashedAssetParams>,
|
||||||
|
callback: (Result<List<HashResult>>) -> Unit
|
||||||
|
) {
|
||||||
throw IllegalStateException("Method not supported on this Android version.")
|
throw IllegalStateException("Method not supported on this Android version.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,15 @@ import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.RequiresExtension
|
import androidx.annotation.RequiresExtension
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.BufferedInputStream
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import java.security.DigestInputStream
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
||||||
|
|
@ -74,20 +79,41 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
||||||
return trashed
|
return trashed
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
|
override fun hashTrashedAssets(
|
||||||
val result = ArrayList<ByteArray?>(trashedAssets.size)
|
trashedAssets: List<TrashedAssetParams>,
|
||||||
for (item in trashedAssets) {
|
callback: (Result<List<HashResult>>) -> Unit
|
||||||
val digest = try {
|
) {
|
||||||
val id = item.id.toLong()
|
if (trashedAssets.isEmpty()) {
|
||||||
val mediaType = item.type.toInt()
|
callback(Result.success(emptyList()))
|
||||||
val uri = contentUriForType(id, mediaType)
|
return
|
||||||
sha1OfUri(ctx, uri)
|
}
|
||||||
} catch (_: Throwable) {
|
hashTask?.cancel()
|
||||||
|
hashTask = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
val results = trashedAssets.map { assetParams ->
|
||||||
|
async {
|
||||||
|
hashSemaphore.withPermit {
|
||||||
|
ensureActive()
|
||||||
|
hashTrashedAsset(assetParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
|
||||||
|
callback(Result.success(results))
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
callback(
|
||||||
|
Result.failure(
|
||||||
|
FlutterError(
|
||||||
|
HASHING_CANCELLED_CODE,
|
||||||
|
"Hashing operation was cancelled",
|
||||||
null
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
callback(Result.failure(e))
|
||||||
}
|
}
|
||||||
result.add(digest)
|
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldFullSync(): Boolean =
|
override fun shouldFullSync(): Boolean =
|
||||||
|
|
@ -144,6 +170,13 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
||||||
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
|
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun hashTrashedAsset(assetParams: TrashedAssetParams): HashResult {
|
||||||
|
val id = assetParams.id.toLong()
|
||||||
|
val mediaType = assetParams.type.toInt()
|
||||||
|
val assetUri = contentUriForType(id, mediaType)
|
||||||
|
return hashAssetFromUri(assetParams.id, assetUri)
|
||||||
|
}
|
||||||
|
|
||||||
private fun contentUriForType(id: Long, mediaType: Int): Uri {
|
private fun contentUriForType(id: Long, mediaType: Int): Uri {
|
||||||
val vol = MediaStore.VOLUME_EXTERNAL
|
val vol = MediaStore.VOLUME_EXTERNAL
|
||||||
val base = when (mediaType) {
|
val base = when (mediaType) {
|
||||||
|
|
@ -154,20 +187,4 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
||||||
}
|
}
|
||||||
return ContentUris.withAppendedId(base, id)
|
return ContentUris.withAppendedId(base, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sha1OfUri(ctx: Context, uri: Uri): ByteArray? {
|
|
||||||
return try {
|
|
||||||
val md = MessageDigest.getInstance("SHA-1")
|
|
||||||
ctx.contentResolver.openInputStream(uri)?.use { input ->
|
|
||||||
DigestInputStream(BufferedInputStream(input), md).use { dis ->
|
|
||||||
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
|
|
||||||
while (dis.read(buf) != -1) { /* pump */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: return null
|
|
||||||
md.digest()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ 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 android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
|
|
@ -30,12 +31,12 @@ sealed class AssetResult {
|
||||||
open class NativeSyncApiImplBase(context: Context) {
|
open class NativeSyncApiImplBase(context: Context) {
|
||||||
private val ctx: Context = context.applicationContext
|
private val ctx: Context = context.applicationContext
|
||||||
|
|
||||||
private var hashTask: Job? = null
|
internal var hashTask: Job? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
||||||
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
internal val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
||||||
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
internal const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
||||||
|
|
||||||
const val MEDIA_SELECTION =
|
const val MEDIA_SELECTION =
|
||||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||||
|
|
@ -274,12 +275,15 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun hashAsset(assetId: String): HashResult {
|
private suspend fun hashAsset(assetId: String): HashResult {
|
||||||
return try {
|
|
||||||
val assetUri = ContentUris.withAppendedId(
|
val assetUri = ContentUris.withAppendedId(
|
||||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||||
assetId.toLong()
|
assetId.toLong()
|
||||||
)
|
)
|
||||||
|
return hashAssetFromUri(assetId, assetUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun hashAssetFromUri(assetId: String, assetUri: Uri): HashResult {
|
||||||
|
return try {
|
||||||
val digest = MessageDigest.getInstance("SHA-1")
|
val digest = MessageDigest.getInstance("SHA-1")
|
||||||
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
||||||
var bytesRead: Int
|
var bytesRead: Int
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@ struct PlatformAsset: Hashable {
|
||||||
var durationInSeconds: Int64
|
var durationInSeconds: Int64
|
||||||
var orientation: Int64
|
var orientation: Int64
|
||||||
var isFavorite: Bool
|
var isFavorite: Bool
|
||||||
|
var size: Int64? = nil
|
||||||
|
|
||||||
|
|
||||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
|
@ -154,6 +155,7 @@ struct PlatformAsset: Hashable {
|
||||||
let durationInSeconds = pigeonVar_list[7] as! Int64
|
let durationInSeconds = pigeonVar_list[7] as! Int64
|
||||||
let orientation = pigeonVar_list[8] as! Int64
|
let orientation = pigeonVar_list[8] as! Int64
|
||||||
let isFavorite = pigeonVar_list[9] as! Bool
|
let isFavorite = pigeonVar_list[9] as! Bool
|
||||||
|
let size: Int64? = nilOrValue(pigeonVar_list[10])
|
||||||
|
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: id,
|
id: id,
|
||||||
|
|
@ -165,7 +167,8 @@ struct PlatformAsset: Hashable {
|
||||||
height: height,
|
height: height,
|
||||||
durationInSeconds: durationInSeconds,
|
durationInSeconds: durationInSeconds,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
isFavorite: isFavorite
|
isFavorite: isFavorite,
|
||||||
|
size: size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func toList() -> [Any?] {
|
func toList() -> [Any?] {
|
||||||
|
|
@ -180,6 +183,7 @@ struct PlatformAsset: Hashable {
|
||||||
durationInSeconds,
|
durationInSeconds,
|
||||||
orientation,
|
orientation,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
size,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
||||||
|
|
@ -300,6 +304,39 @@ struct HashResult: Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generated class from Pigeon that represents data sent in messages.
|
||||||
|
struct TrashedAssetParams: Hashable {
|
||||||
|
var id: String
|
||||||
|
var type: Int64
|
||||||
|
var albumId: String? = nil
|
||||||
|
|
||||||
|
|
||||||
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
static func fromList(_ pigeonVar_list: [Any?]) -> TrashedAssetParams? {
|
||||||
|
let id = pigeonVar_list[0] as! String
|
||||||
|
let type = pigeonVar_list[1] as! Int64
|
||||||
|
let albumId: String? = nilOrValue(pigeonVar_list[2])
|
||||||
|
|
||||||
|
return TrashedAssetParams(
|
||||||
|
id: id,
|
||||||
|
type: type,
|
||||||
|
albumId: albumId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func toList() -> [Any?] {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
albumId,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
static func == (lhs: TrashedAssetParams, rhs: TrashedAssetParams) -> Bool {
|
||||||
|
return deepEqualsMessages(lhs.toList(), rhs.toList()) }
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
deepHashMessages(value: toList(), hasher: &hasher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
||||||
override func readValue(ofType type: UInt8) -> Any? {
|
override func readValue(ofType type: UInt8) -> Any? {
|
||||||
switch type {
|
switch type {
|
||||||
|
|
@ -311,6 +348,8 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
||||||
return SyncDelta.fromList(self.readValue() as! [Any?])
|
return SyncDelta.fromList(self.readValue() as! [Any?])
|
||||||
case 132:
|
case 132:
|
||||||
return HashResult.fromList(self.readValue() as! [Any?])
|
return HashResult.fromList(self.readValue() as! [Any?])
|
||||||
|
case 133:
|
||||||
|
return TrashedAssetParams.fromList(self.readValue() as! [Any?])
|
||||||
default:
|
default:
|
||||||
return super.readValue(ofType: type)
|
return super.readValue(ofType: type)
|
||||||
}
|
}
|
||||||
|
|
@ -331,6 +370,9 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
||||||
} else if let value = value as? HashResult {
|
} else if let value = value as? HashResult {
|
||||||
super.writeByte(132)
|
super.writeByte(132)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
|
} else if let value = value as? TrashedAssetParams {
|
||||||
|
super.writeByte(133)
|
||||||
|
super.writeValue(value.toList())
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(value)
|
super.writeValue(value)
|
||||||
}
|
}
|
||||||
|
|
@ -364,6 +406,8 @@ protocol NativeSyncApi {
|
||||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||||
func cancelHashing() throws
|
func cancelHashing() throws
|
||||||
|
func getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||||
|
func hashTrashedAssets(trashedAssets: [TrashedAssetParams], completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
|
|
@ -532,5 +576,42 @@ class NativeSyncApiSetup {
|
||||||
} else {
|
} else {
|
||||||
cancelHashingChannel.setMessageHandler(nil)
|
cancelHashingChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
|
let getTrashedAssetsForAlbumChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let albumIdArg = args[0] as! String
|
||||||
|
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
|
||||||
|
do {
|
||||||
|
let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg)
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getTrashedAssetsForAlbumChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
|
let hashTrashedAssetsChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
hashTrashedAssetsChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let trashedAssetsArg = args[0] as! [TrashedAssetParams]
|
||||||
|
api.hashTrashedAssets(trashedAssets: trashedAssetsArg) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let res):
|
||||||
|
reply(wrapResult(res))
|
||||||
|
case .failure(let error):
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hashTrashedAssetsChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class HashService {
|
||||||
_localAssetRepository = localAssetRepository,
|
_localAssetRepository = localAssetRepository,
|
||||||
_cancelChecker = cancelChecker,
|
_cancelChecker = cancelChecker,
|
||||||
_nativeSyncApi = nativeSyncApi,
|
_nativeSyncApi = nativeSyncApi,
|
||||||
_batchSize = batchSize ?? kBatchHashFileLimit;
|
_batchSize = batchSize ?? kBatchHashFileLimit,
|
||||||
_trashSyncService = trashSyncService;
|
_trashSyncService = trashSyncService;
|
||||||
|
|
||||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||||
|
|
@ -144,7 +144,6 @@ class HashService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
|
Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
|
||||||
int bytesProcessed = 0;
|
|
||||||
final toHash = <TrashedAsset>[];
|
final toHash = <TrashedAsset>[];
|
||||||
|
|
||||||
for (final asset in assetsToHash) {
|
for (final asset in assetsToHash) {
|
||||||
|
|
@ -160,13 +159,11 @@ class HashService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesProcessed += asset.size!;
|
|
||||||
toHash.add(asset);
|
toHash.add(asset);
|
||||||
|
|
||||||
if (toHash.length >= batchFileLimit || bytesProcessed >= batchSizeLimit) {
|
if (toHash.length == _batchSize) {
|
||||||
await _processTrashedBatch(album, toHash);
|
await _processTrashedBatch(album, toHash);
|
||||||
toHash.clear();
|
toHash.clear();
|
||||||
bytesProcessed = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,27 +178,27 @@ class HashService {
|
||||||
_log.fine("Hashing ${toHash.length} trashed files");
|
_log.fine("Hashing ${toHash.length} trashed files");
|
||||||
|
|
||||||
final params = toHash.map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)).toList();
|
final params = toHash.map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)).toList();
|
||||||
final hashes = await _nativeSyncApi.hashTrashedAssets(params);
|
final hashResults = await _nativeSyncApi.hashTrashedAssets(params);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
hashes.length == toHash.length,
|
hashResults.length == toHash.length,
|
||||||
"Trashed Assets, Hashes length does not match toHash length: ${hashes.length} != ${toHash.length}",
|
"Trashed Assets, Hashes length does not match toHash length: ${hashResults.length} != ${toHash.length}",
|
||||||
);
|
);
|
||||||
final hashed = <TrashedAsset>[];
|
final hashed = <TrashedAsset>[];
|
||||||
|
|
||||||
for (int i = 0; i < hashes.length; i++) {
|
for (int i = 0; i < hashResults.length; i++) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
_log.warning("Hashing cancelled. Stopped processing batch.");
|
_log.warning("Hashing cancelled. Stopped processing batch.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final hash = hashes[i];
|
final hashResult = hashResults[i];
|
||||||
final asset = toHash[i];
|
final asset = toHash[i];
|
||||||
if (hash?.length == 20) {
|
if (hashResult.hash != null) {
|
||||||
hashed.add(asset.copyWith(checksum: base64.encode(hash!)));
|
hashed.add(asset.copyWith(checksum: hashResult.hash!));
|
||||||
} else {
|
} else {
|
||||||
_log.warning(
|
_log.warning(
|
||||||
"Failed to hash trashed file for ${asset.id}: ${asset.name} created at ${asset.createdAt} from album: ${album.name}",
|
"Failed to hash trashed asset with id: ${hashResult.assetId}, name: ${asset.name}, createdAt: ${asset.createdAt}, from album: ${album.name}. Error: ${hashResult.error ?? "unknown"}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,61 +206,4 @@ class HashService {
|
||||||
_log.fine("Hashed ${hashed.length}/${toHash.length} trashed assets");
|
_log.fine("Hashed ${hashed.length}/${toHash.length} trashed assets");
|
||||||
await _trashSyncService.updateChecksums(hashed);
|
await _trashSyncService.updateChecksums(hashed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
|
|
||||||
// final trashedAssets = assetsToHash
|
|
||||||
// .map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id))
|
|
||||||
// .toList();
|
|
||||||
//
|
|
||||||
// final byId = <String, LocalAsset>{for (final a in assetsToHash) a.id: a};
|
|
||||||
//
|
|
||||||
// final hashed = <LocalAsset>[];
|
|
||||||
// const chunkSize = 10;
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < trashedAssets.length; i += chunkSize) {
|
|
||||||
// if (isCancelled) {
|
|
||||||
// _log.warning("Hashing cancelled. Stopped processing assets.");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// final end = (i + chunkSize <= trashedAssets.length) ? i + chunkSize : trashedAssets.length;
|
|
||||||
// final batch = trashedAssets.sublist(i, end);
|
|
||||||
//
|
|
||||||
// List<Uint8List?> hashes;
|
|
||||||
// try {
|
|
||||||
// hashes = await _nativeSyncApi.hashTrashedAssets(batch);
|
|
||||||
// } catch (e, s) {
|
|
||||||
// _log.severe("hashTrashedAssets failed for batch [$i..${end - 1}]: $e", e, s);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (hashes.length != batch.length) {
|
|
||||||
// _log.warning(
|
|
||||||
// "hashTrashedAssets returned ${hashes.length} hashes for ${batch.length} assets (batch [$i..${end - 1}]).",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final limit = hashes.length < batch.length ? hashes.length : batch.length;
|
|
||||||
// for (int j = 0; j < limit; j++) {
|
|
||||||
// if (isCancelled) {
|
|
||||||
// _log.warning("Hashing cancelled. Stopped processing assets.");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// final asset = batch[j];
|
|
||||||
// final hash = hashes[j];
|
|
||||||
//
|
|
||||||
// if (hash != null && hash.length == 20) {
|
|
||||||
// final localAsset = byId[asset.id];
|
|
||||||
// if (localAsset != null) {
|
|
||||||
// hashed.add(localAsset.copyWith(checksum: base64.encode(hash)));
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// _log.warning("Failed to hash file for ${asset.id} from album: ${album.name}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _log.warning("updateHashes for album: ${album.name}, assets: ${hashed.map((e) => '${e.name}-${e.checksum}')}");
|
|
||||||
//
|
|
||||||
// await _trashSyncService.updateHashes(hashed);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
118
mobile/lib/platform/native_sync_api.g.dart
generated
118
mobile/lib/platform/native_sync_api.g.dart
generated
|
|
@ -41,6 +41,7 @@ class PlatformAsset {
|
||||||
required this.durationInSeconds,
|
required this.durationInSeconds,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
|
this.size,
|
||||||
});
|
});
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
|
|
@ -63,8 +64,22 @@ class PlatformAsset {
|
||||||
|
|
||||||
bool isFavorite;
|
bool isFavorite;
|
||||||
|
|
||||||
|
int? size;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite];
|
return <Object?>[
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
durationInSeconds,
|
||||||
|
orientation,
|
||||||
|
isFavorite,
|
||||||
|
size,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
|
|
@ -84,6 +99,7 @@ class PlatformAsset {
|
||||||
durationInSeconds: result[7]! as int,
|
durationInSeconds: result[7]! as int,
|
||||||
orientation: result[8]! as int,
|
orientation: result[8]! as int,
|
||||||
isFavorite: result[9]! as bool,
|
isFavorite: result[9]! as bool,
|
||||||
|
size: result[10] as int?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,6 +260,45 @@ class HashResult {
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TrashedAssetParams {
|
||||||
|
TrashedAssetParams({required this.id, required this.type, this.albumId});
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
int type;
|
||||||
|
|
||||||
|
String? albumId;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[id, type, albumId];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static TrashedAssetParams decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return TrashedAssetParams(id: result[0]! as String, type: result[1]! as int, albumId: result[2] as String?);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! TrashedAssetParams || other.runtimeType != runtimeType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (identical(this, other)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _deepEquals(encode(), other.encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
int get hashCode => Object.hashAll(_toList());
|
||||||
|
}
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
|
|
@ -263,6 +318,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||||
} else if (value is HashResult) {
|
} else if (value is HashResult) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is TrashedAssetParams) {
|
||||||
|
buffer.putUint8(133);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(buffer, value);
|
super.writeValue(buffer, value);
|
||||||
}
|
}
|
||||||
|
|
@ -279,6 +337,8 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||||
return SyncDelta.decode(readValue(buffer)!);
|
return SyncDelta.decode(readValue(buffer)!);
|
||||||
case 132:
|
case 132:
|
||||||
return HashResult.decode(readValue(buffer)!);
|
return HashResult.decode(readValue(buffer)!);
|
||||||
|
case 133:
|
||||||
|
return TrashedAssetParams.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
}
|
}
|
||||||
|
|
@ -562,4 +622,60 @@ class NativeSyncApi {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId, updatedTimeCond]);
|
||||||
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformAsset>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<HashResult>> hashTrashedAssets(List<TrashedAssetParams> trashedAssets) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[trashedAssets]);
|
||||||
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HashResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ abstract class NativeSyncApi {
|
||||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond});
|
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond});
|
||||||
|
|
||||||
|
@async
|
||||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||||
List<Uint8List?> hashTrashedAssets(List<TrashedAssetParams> trashedAssets);
|
List<HashResult> hashTrashedAssets(List<TrashedAssetParams> trashedAssets);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue